From 0a0f4d9700aa2816c5fed5b551df19fe9b6321a6 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 31 May 2023 15:56:30 +0200 Subject: [PATCH 01/27] scalar constructor --- src/lib/core.ts | 5 ++++ src/lib/field.ts | 9 +++++++ src/lib/scalar.ts | 48 +++++++++++++++++++++++++++++++++++++ src/lib/scalar.unit-test.ts | 11 +++++++++ 4 files changed, 73 insertions(+) create mode 100644 src/lib/scalar.ts create mode 100644 src/lib/scalar.unit-test.ts diff --git a/src/lib/core.ts b/src/lib/core.ts index aae0a5207..43e279bd7 100644 --- a/src/lib/core.ts +++ b/src/lib/core.ts @@ -3,6 +3,7 @@ import { defineBinable } from '../bindings/lib/binable.js'; import { sizeInBits } from '../provable/field-bigint.js'; import { Bool, Scalar, Group } from '../snarky.js'; import { Field as InternalField } from './field.js'; +import { Scalar as InternalScalar } from './scalar.js'; import { Scalar as ScalarBigint } from '../provable/curve-bigint.js'; import { mod } from '../bindings/crypto/finite_field.js'; @@ -44,6 +45,10 @@ export { Field, Bool, Scalar, Group }; const Field = toFunctionConstructor(InternalField); type Field = InternalField; +// TODO +// const Scalar = toFunctionConstructor(InternalScalar); +// type Scalar = InternalScalar; + function toFunctionConstructor any>( Class: Class ): Class & ((...args: InferArgs) => InferReturn) { diff --git a/src/lib/field.ts b/src/lib/field.ts index e531df262..4591b5dfb 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -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)), @@ -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]; diff --git a/src/lib/scalar.ts b/src/lib/scalar.ts new file mode 100644 index 000000000..adf6e3402 --- /dev/null +++ b/src/lib/scalar.ts @@ -0,0 +1,48 @@ +import { Scalar as Fq } from '../provable/curve-bigint.js'; +import { FieldConst, FieldVar } from './field.js'; + +export { Scalar, ScalarConst, unshift }; + +type BoolVar = FieldVar; +type ScalarConst = Uint8Array; + +const ScalarConst = { + fromBigint: constFromBigint, + toBigint: constToBigint, +}; + +let shift = Fq(1n + 2n ** 255n); +let oneHalf = Fq.inverse(2n)!; + +class Scalar { + bits: BoolVar[]; + constantValue?: ScalarConst; + + constructor(bits: BoolVar[], constantValue?: ScalarConst) { + this.bits = bits; + this.constantValue = constantValue ?? toConstantScalar(bits); + } +} + +function toConstantScalar(bits: BoolVar[]) { + let constantBits = Array(bits.length); + for (let i = 0; i < bits.length; i++) { + let bool = bits[i]; + if (!FieldVar.isConstant(bool)) return undefined; + constantBits[i] = FieldConst.equal(bool[1], FieldConst[1]); + } + let sShifted = Fq.fromBits(constantBits); + let s = unshift(sShifted); + return constFromBigint(s); +} + +function unshift(s: Fq): Fq { + return Fq.mul(Fq.sub(s, shift), oneHalf); +} + +function constToBigint(x: ScalarConst): Fq { + return Fq.fromBytes([...x]); +} +function constFromBigint(x: Fq) { + return Uint8Array.from(Fq.toBytes(x)); +} diff --git a/src/lib/scalar.unit-test.ts b/src/lib/scalar.unit-test.ts new file mode 100644 index 000000000..30095d7a8 --- /dev/null +++ b/src/lib/scalar.unit-test.ts @@ -0,0 +1,11 @@ +import { Scalar as Fq } from '../provable/curve-bigint.js'; +import { FieldVar } from './field.js'; +import { Scalar, ScalarConst, unshift } from './scalar.js'; + +let s0 = Fq.random(); +let bits = Fq.toBits(s0).map((b) => FieldVar.constant(BigInt(b))); + +let s = new Scalar(bits); + +console.log(unshift(s0) === ScalarConst.toBigint(s.constantValue!)); +// console.log(s.bits); From b3511e52dee265bf35a0a4be85b773f2924b93c7 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 31 May 2023 16:16:22 +0200 Subject: [PATCH 02/27] to fields compressed --- src/lib/scalar.ts | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/lib/scalar.ts b/src/lib/scalar.ts index adf6e3402..9f0b106df 100644 --- a/src/lib/scalar.ts +++ b/src/lib/scalar.ts @@ -1,5 +1,6 @@ +import { Snarky, Provable, Bool } from '../snarky.js'; import { Scalar as Fq } from '../provable/curve-bigint.js'; -import { FieldConst, FieldVar } from './field.js'; +import { Field, FieldConst, FieldVar } from './field.js'; export { Scalar, ScalarConst, unshift }; @@ -14,6 +15,9 @@ const ScalarConst = { let shift = Fq(1n + 2n ** 255n); let oneHalf = Fq.inverse(2n)!; +/** + * Represents a {@link Scalar}. + */ class Scalar { bits: BoolVar[]; constantValue?: ScalarConst; @@ -22,6 +26,43 @@ class Scalar { this.bits = bits; this.constantValue = constantValue ?? toConstantScalar(bits); } + + /** + * Serialize this Scalar to Field elements. Part of the {@link Provable} interface. + * + * **Warning**: This function is for internal usage. It returns 255 field elements + * which represent the Scalar in a shifted, bitwise format. + * Check out {@link Scalar.toFieldsCompressed} for a user-friendly serialization + * that can be used outside proofs. + */ + toFields(): Field[] { + return this.bits.map((b) => new Field(b)); + } + + /** + * Serialize a Scalar into a Field element plus one bit, where the bit is represented as a Bool. + * + * **Warning**: This function is not available for provable code. + * + * Note: Since the Scalar field is slightly larger than the base Field, an additional high bit + * is needed to represent all Scalars. However, for a random Scalar, the high bit will be `false` with overwhelming probability. + */ + toFieldsCompressed(): { field: Field; highBit: Bool } { + let isConstant = this.bits.every((b) => FieldVar.isConstant(b)); + let constantValue = this.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 = ScalarConst.toBigint(constantValue); + let lowBitSize = BigInt(Fq.sizeInBits - 1); + let lowBitMask = (1n << lowBitSize) - 1n; + return { + field: new Field(x & lowBitMask), + highBit: Bool(x >> lowBitSize === 1n), + }; + } } function toConstantScalar(bits: BoolVar[]) { From b30592026affc2cf34b7165bae78c767f2d0bd71 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 31 May 2023 17:12:44 +0200 Subject: [PATCH 03/27] constant scalar ops --- src/lib/scalar.ts | 120 ++++++++++++++++++++++++++++++------ src/lib/scalar.unit-test.ts | 2 +- 2 files changed, 103 insertions(+), 19 deletions(-) diff --git a/src/lib/scalar.ts b/src/lib/scalar.ts index 9f0b106df..56f257177 100644 --- a/src/lib/scalar.ts +++ b/src/lib/scalar.ts @@ -12,7 +12,7 @@ const ScalarConst = { toBigint: constToBigint, }; -let shift = Fq(1n + 2n ** 255n); +let scalarShift = Fq(1n + 2n ** 255n); let oneHalf = Fq.inverse(2n)!; /** @@ -20,13 +20,18 @@ let oneHalf = Fq.inverse(2n)!; */ class Scalar { bits: BoolVar[]; - constantValue?: ScalarConst; + constantValue?: Fq; - constructor(bits: BoolVar[], constantValue?: ScalarConst) { + constructor(bits: BoolVar[], constantValue?: Fq) { this.bits = bits; this.constantValue = constantValue ?? toConstantScalar(bits); } + static fromBigint(scalar: Fq) { + let bits = toBits(scalar); + return new Scalar(bits, scalar); + } + /** * Serialize this Scalar to Field elements. Part of the {@link Provable} interface. * @@ -39,33 +44,98 @@ class Scalar { return this.bits.map((b) => new Field(b)); } + // operations on constant scalars + + #assertConstant(name: string): Fq { + if (this.constantValue === undefined) + throw Error( + `Scalar.${name}() 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.` + ); + return this.constantValue; + } + /** * Serialize a Scalar into a Field element plus one bit, where the bit is represented as a Bool. * - * **Warning**: This function is not available for provable code. + * **Warning**: This method is not available for provable code. * * Note: Since the Scalar field is slightly larger than the base Field, an additional high bit * is needed to represent all Scalars. However, for a random Scalar, the high bit will be `false` with overwhelming probability. */ toFieldsCompressed(): { field: Field; highBit: Bool } { - let isConstant = this.bits.every((b) => FieldVar.isConstant(b)); - let constantValue = this.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 = ScalarConst.toBigint(constantValue); + let s = this.#assertConstant('toFieldsCompressed'); let lowBitSize = BigInt(Fq.sizeInBits - 1); let lowBitMask = (1n << lowBitSize) - 1n; return { - field: new Field(x & lowBitMask), - highBit: Bool(x >> lowBitSize === 1n), + field: new Field(s & lowBitMask), + highBit: Bool(s >> lowBitSize === 1n), }; } + + /** + * Negate a scalar field element. + * + * **Warning**: This method is not available for provable code. + */ + neg() { + let x = this.#assertConstant('neg'); + let z = Fq.negate(x); + return Scalar.fromBigint(z); + } + + /** + * Add scalar field elements. + * + * **Warning**: This method is not available for provable code. + */ + add(y: Scalar) { + let x = this.#assertConstant('add'); + let y0 = y.#assertConstant('add'); + let z = Fq.add(x, y0); + return Scalar.fromBigint(z); + } + + /** + * Subtract scalar field elements. + * + * **Warning**: This method is not available for provable code. + */ + sub(y: Scalar) { + let x = this.#assertConstant('sub'); + let y0 = y.#assertConstant('sub'); + let z = Fq.sub(x, y0); + return Scalar.fromBigint(z); + } + + /** + * Multiply scalar field elements. + * + * **Warning**: This method is not available for provable code. + */ + mul(y: Scalar) { + let x = this.#assertConstant('mul'); + let y0 = y.#assertConstant('mul'); + let z = Fq.mul(x, y0); + return Scalar.fromBigint(z); + } + + /** + * Divide scalar field elements. + * Throws if the denominator is zero. + * + * **Warning**: This method is not available for provable code. + */ + div(y: Scalar) { + let x = this.#assertConstant('div'); + let y0 = y.#assertConstant('div'); + let z = Fq.div(x, y0); + if (z === undefined) throw Error('Scalar.div(): Division by zero'); + return Scalar.fromBigint(z); + } } -function toConstantScalar(bits: BoolVar[]) { +function toConstantScalar(bits: BoolVar[]): Fq | undefined { let constantBits = Array(bits.length); for (let i = 0; i < bits.length; i++) { let bool = bits[i]; @@ -73,12 +143,26 @@ function toConstantScalar(bits: BoolVar[]) { constantBits[i] = FieldConst.equal(bool[1], FieldConst[1]); } let sShifted = Fq.fromBits(constantBits); - let s = unshift(sShifted); - return constFromBigint(s); + return unshift(sShifted); +} +function toBits(constantValue: Fq): BoolVar[] { + return Fq.toBits(shift(constantValue)).map((b) => + FieldVar.constant(BigInt(b)) + ); +} + +/** + * s -> 2s + 1 + 2^255 + */ +function shift(s: Fq): Fq { + return Fq.add(Fq.add(s, s), scalarShift); } +/** + * inverse of shift, 2s + 1 + 2^255 -> s + */ function unshift(s: Fq): Fq { - return Fq.mul(Fq.sub(s, shift), oneHalf); + return Fq.mul(Fq.sub(s, scalarShift), oneHalf); } function constToBigint(x: ScalarConst): Fq { diff --git a/src/lib/scalar.unit-test.ts b/src/lib/scalar.unit-test.ts index 30095d7a8..e6c7e5a32 100644 --- a/src/lib/scalar.unit-test.ts +++ b/src/lib/scalar.unit-test.ts @@ -7,5 +7,5 @@ let bits = Fq.toBits(s0).map((b) => FieldVar.constant(BigInt(b))); let s = new Scalar(bits); -console.log(unshift(s0) === ScalarConst.toBigint(s.constantValue!)); +console.log(unshift(s0) === s.constantValue!); // console.log(s.bits); From f18dac4b7902665ec96a2d72a1138ad9bb85da8d Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 31 May 2023 17:30:46 +0200 Subject: [PATCH 04/27] provable scalar --- src/lib/scalar.ts | 122 +++++++++++++++++++++++++++--------- src/lib/scalar.unit-test.ts | 4 ++ 2 files changed, 96 insertions(+), 30 deletions(-) diff --git a/src/lib/scalar.ts b/src/lib/scalar.ts index 56f257177..6b77675f9 100644 --- a/src/lib/scalar.ts +++ b/src/lib/scalar.ts @@ -32,18 +32,6 @@ class Scalar { return new Scalar(bits, scalar); } - /** - * Serialize this Scalar to Field elements. Part of the {@link Provable} interface. - * - * **Warning**: This function is for internal usage. It returns 255 field elements - * which represent the Scalar in a shifted, bitwise format. - * Check out {@link Scalar.toFieldsCompressed} for a user-friendly serialization - * that can be used outside proofs. - */ - toFields(): Field[] { - return this.bits.map((b) => new Field(b)); - } - // operations on constant scalars #assertConstant(name: string): Fq { @@ -55,24 +43,6 @@ That means it can't be called in a @method or similar environment, and there's n return this.constantValue; } - /** - * Serialize a Scalar into a Field element plus one bit, where the bit is represented as a Bool. - * - * **Warning**: This method is not available for provable code. - * - * Note: Since the Scalar field is slightly larger than the base Field, an additional high bit - * is needed to represent all Scalars. However, for a random Scalar, the high bit will be `false` with overwhelming probability. - */ - toFieldsCompressed(): { field: Field; highBit: Bool } { - let s = this.#assertConstant('toFieldsCompressed'); - let lowBitSize = BigInt(Fq.sizeInBits - 1); - let lowBitMask = (1n << lowBitSize) - 1n; - return { - field: new Field(s & lowBitMask), - highBit: Bool(s >> lowBitSize === 1n), - }; - } - /** * Negate a scalar field element. * @@ -133,6 +103,97 @@ That means it can't be called in a @method or similar environment, and there's n if (z === undefined) throw Error('Scalar.div(): Division by zero'); return Scalar.fromBigint(z); } + + /** + * Serialize a Scalar into a Field element plus one bit, where the bit is represented as a Bool. + * + * **Warning**: This method is not available for provable code. + * + * Note: Since the Scalar field is slightly larger than the base Field, an additional high bit + * is needed to represent all Scalars. However, for a random Scalar, the high bit will be `false` with overwhelming probability. + */ + toFieldsCompressed(): { field: Field; highBit: Bool } { + let s = this.#assertConstant('toFieldsCompressed'); + let lowBitSize = BigInt(Fq.sizeInBits - 1); + let lowBitMask = (1n << lowBitSize) - 1n; + return { + field: new Field(s & lowBitMask), + highBit: Bool(s >> lowBitSize === 1n), + }; + } + + // internal stuff + + // Provable + + /** + * Part of the {@link Provable} interface. + * + * Serialize a {@link Scalar} into an array of {@link Field} elements. + */ + static toFields(x: Scalar) { + return x.bits.map((b) => new Field(b)); + } + + /** + * Serialize this Scalar to Field elements. + * + * **Warning**: This function is for internal usage. It returns 255 field elements + * which represent the Scalar in a shifted, bitwise format. + * Check out {@link Scalar.toFieldsCompressed} for a user-friendly serialization + * that can be used outside proofs. + */ + toFields(): Field[] { + return Scalar.toFields(this); + } + + /** + * Part of the {@link Provable} interface. + * + * Serialize a {@link Scalar} into its auxiliary data, which are empty. + */ + static toAuxiliary() { + return []; + } + + /** + * Part of the {@link Provable} interface. + * + * Creates a data structure from an array of serialized {@link Field} elements. + */ + static fromFields(fields: Field[]): Scalar { + return new Scalar(fields.map((x) => x.value)); + } + + /** + * Part of the {@link Provable} interface. + * + * Returns the size of this type in {@link Field} elements. + */ + static sizeInFields(): number { + return Fq.sizeInBits; + } + + /** + * Part of the {@link Provable} interface. + * + * Does nothing. + */ + static check() { + /* It is not necessary to boolean constrain the bits of a scalar for the following + reasons: + + The only type-safe functions which can be called with a scalar value are + + - if + - assertEqual + - equal + - Group.scale + + The only one of these whose behavior depends on the bit values of the input scalars + is Group.scale, and that function boolean constrains the scalar input itself. + */ + } } function toConstantScalar(bits: BoolVar[]): Fq | undefined { @@ -145,6 +206,7 @@ function toConstantScalar(bits: BoolVar[]): Fq | undefined { let sShifted = Fq.fromBits(constantBits); return unshift(sShifted); } + function toBits(constantValue: Fq): BoolVar[] { return Fq.toBits(shift(constantValue)).map((b) => FieldVar.constant(BigInt(b)) diff --git a/src/lib/scalar.unit-test.ts b/src/lib/scalar.unit-test.ts index e6c7e5a32..576fb6075 100644 --- a/src/lib/scalar.unit-test.ts +++ b/src/lib/scalar.unit-test.ts @@ -1,7 +1,11 @@ +import { Provable } from '../snarky.js'; import { Scalar as Fq } from '../provable/curve-bigint.js'; import { FieldVar } from './field.js'; import { Scalar, ScalarConst, unshift } from './scalar.js'; +// types +Scalar satisfies Provable; + let s0 = Fq.random(); let bits = Fq.toBits(s0).map((b) => FieldVar.constant(BigInt(b))); From 925714ce65c5b8d1d93cd006ed283688d40a87e1 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 31 May 2023 18:08:11 +0200 Subject: [PATCH 05/27] from bits --- src/lib/scalar.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/lib/scalar.ts b/src/lib/scalar.ts index 6b77675f9..85a9bd252 100644 --- a/src/lib/scalar.ts +++ b/src/lib/scalar.ts @@ -32,6 +32,10 @@ class Scalar { return new Scalar(bits, scalar); } + static fromBits(bits: Bool[]) { + return Scalar.fromFields(bits.map((b) => b.toField())); + } + // operations on constant scalars #assertConstant(name: string): Fq { @@ -130,6 +134,10 @@ That means it can't be called in a @method or similar environment, and there's n * Part of the {@link Provable} interface. * * Serialize a {@link Scalar} into an array of {@link Field} elements. + * + * **Warning**: This function is for internal usage. It returns 255 field elements + * which represent the Scalar in a shifted, bitwise format. + * The fields are not constrained to be boolean. */ static toFields(x: Scalar) { return x.bits.map((b) => new Field(b)); @@ -140,6 +148,8 @@ That means it can't be called in a @method or similar environment, and there's n * * **Warning**: This function is for internal usage. It returns 255 field elements * which represent the Scalar in a shifted, bitwise format. + * The fields are not constrained to be boolean. + * * Check out {@link Scalar.toFieldsCompressed} for a user-friendly serialization * that can be used outside proofs. */ @@ -183,7 +193,7 @@ That means it can't be called in a @method or similar environment, and there's n /* It is not necessary to boolean constrain the bits of a scalar for the following reasons: - The only type-safe functions which can be called with a scalar value are + The only provable methods which can be called with a scalar value are - if - assertEqual From 7bc1fbed645824591975d13f49ef861554239652 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 31 May 2023 18:08:25 +0200 Subject: [PATCH 06/27] fix api example --- src/examples/api_exploration.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/examples/api_exploration.ts b/src/examples/api_exploration.ts index 4abba290f..427e37065 100644 --- a/src/examples/api_exploration.ts +++ b/src/examples/api_exploration.ts @@ -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(); From 66b12fd93b8194f7e61d47ee5bef24b5ccd170a4 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 31 May 2023 23:04:22 +0200 Subject: [PATCH 07/27] remaining methods --- src/lib/scalar.ts | 65 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/src/lib/scalar.ts b/src/lib/scalar.ts index 85a9bd252..b669ffd1b 100644 --- a/src/lib/scalar.ts +++ b/src/lib/scalar.ts @@ -27,15 +27,42 @@ class Scalar { this.constantValue = constantValue ?? toConstantScalar(bits); } - static fromBigint(scalar: Fq) { + /** + * Create a constant {@link Scalar} from a bigint, number, string or Scalar. + * + * If the input is too large, it is reduced modulo the scalar field size. + */ + static from(x: Scalar | bigint | number | string) { + if (x instanceof Scalar) return x; + let scalar = Fq(x); let bits = toBits(scalar); return new Scalar(bits, scalar); } + /** + * @deprecated use {@link Scalar.from} + */ + static fromBigint(x: bigint) { + return Scalar.from(x); + } + + /** + * Creates a data structure from an array of serialized {@link Bool}. + * + * **Warning**: The bits are interpreted as the bits of 2s + 1 + 2^255, where s is the Scalar. + */ static fromBits(bits: Bool[]) { return Scalar.fromFields(bits.map((b) => b.toField())); } + /** + * Returns a random {@link Scalar}. + * Randomness can not be proven inside a circuit! + */ + static random() { + return Scalar.from(Fq.random()); + } + // operations on constant scalars #assertConstant(name: string): Fq { @@ -55,7 +82,7 @@ That means it can't be called in a @method or similar environment, and there's n neg() { let x = this.#assertConstant('neg'); let z = Fq.negate(x); - return Scalar.fromBigint(z); + return Scalar.from(z); } /** @@ -67,7 +94,7 @@ That means it can't be called in a @method or similar environment, and there's n let x = this.#assertConstant('add'); let y0 = y.#assertConstant('add'); let z = Fq.add(x, y0); - return Scalar.fromBigint(z); + return Scalar.from(z); } /** @@ -79,7 +106,7 @@ That means it can't be called in a @method or similar environment, and there's n let x = this.#assertConstant('sub'); let y0 = y.#assertConstant('sub'); let z = Fq.sub(x, y0); - return Scalar.fromBigint(z); + return Scalar.from(z); } /** @@ -91,7 +118,7 @@ That means it can't be called in a @method or similar environment, and there's n let x = this.#assertConstant('mul'); let y0 = y.#assertConstant('mul'); let z = Fq.mul(x, y0); - return Scalar.fromBigint(z); + return Scalar.from(z); } /** @@ -105,7 +132,7 @@ That means it can't be called in a @method or similar environment, and there's n let y0 = y.#assertConstant('div'); let z = Fq.div(x, y0); if (z === undefined) throw Error('Scalar.div(): Division by zero'); - return Scalar.fromBigint(z); + return Scalar.from(z); } /** @@ -204,6 +231,32 @@ That means it can't be called in a @method or similar environment, and there's n is Group.scale, and that function boolean constrains the scalar input itself. */ } + + // ProvableExtended + + /** + * Serialize a {@link Scalar} to a JSON string. + * This operation does _not_ affect the circuit and can't be used to prove anything about the string representation of the Scalar. + */ + static toJSON(x: Scalar) { + return x.toJSON(); + } + + /** + * Serializes this Scalar to a string + */ + toJSON() { + let s = this.#assertConstant('toJSON'); + return s.toString(); + } + + /** + * Deserialize a JSON structure into a {@link Scalar}. + * This operation does _not_ affect the circuit and can't be used to prove anything about the string representation of the Scalar. + */ + static fromJSON(x: string) { + return Scalar.from(Fq.fromJSON(x)); + } } function toConstantScalar(bits: BoolVar[]): Fq | undefined { From c4c3b20dc0d4c7581f2715534930ec2f6bdcc745 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 1 Jun 2023 09:48:32 +0200 Subject: [PATCH 08/27] move back to ml-equivalent repr for Group.scale compatibility --- src/lib/scalar.ts | 15 +++++++++++---- src/lib/scalar.unit-test.ts | 6 +++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/lib/scalar.ts b/src/lib/scalar.ts index b669ffd1b..760af2f77 100644 --- a/src/lib/scalar.ts +++ b/src/lib/scalar.ts @@ -2,7 +2,7 @@ import { Snarky, Provable, Bool } from '../snarky.js'; import { Scalar as Fq } from '../provable/curve-bigint.js'; import { Field, FieldConst, FieldVar } from './field.js'; -export { Scalar, ScalarConst, unshift }; +export { Scalar, ScalarConst, unshift, shift }; type BoolVar = FieldVar; type ScalarConst = Uint8Array; @@ -20,11 +20,14 @@ let oneHalf = Fq.inverse(2n)!; */ class Scalar { bits: BoolVar[]; - constantValue?: Fq; + constantValue?: ScalarConst; constructor(bits: BoolVar[], constantValue?: Fq) { this.bits = bits; - this.constantValue = constantValue ?? toConstantScalar(bits); + constantValue ??= toConstantScalar(bits); + if (constantValue !== undefined) { + this.constantValue = ScalarConst.fromBigint(constantValue); + } } /** @@ -46,6 +49,10 @@ class Scalar { return Scalar.from(x); } + toBigInt() { + return this.#assertConstant('toBigInt'); + } + /** * Creates a data structure from an array of serialized {@link Bool}. * @@ -71,7 +78,7 @@ class Scalar { `Scalar.${name}() 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.` ); - return this.constantValue; + return ScalarConst.toBigint(this.constantValue); } /** diff --git a/src/lib/scalar.unit-test.ts b/src/lib/scalar.unit-test.ts index 576fb6075..a2eada241 100644 --- a/src/lib/scalar.unit-test.ts +++ b/src/lib/scalar.unit-test.ts @@ -1,15 +1,15 @@ import { Provable } from '../snarky.js'; import { Scalar as Fq } from '../provable/curve-bigint.js'; import { FieldVar } from './field.js'; -import { Scalar, ScalarConst, unshift } from './scalar.js'; +import { Scalar, ScalarConst, shift, unshift } from './scalar.js'; // types Scalar satisfies Provable; let s0 = Fq.random(); -let bits = Fq.toBits(s0).map((b) => FieldVar.constant(BigInt(b))); +let bits = Fq.toBits(shift(s0)).map((b) => FieldVar.constant(BigInt(b))); let s = new Scalar(bits); -console.log(unshift(s0) === s.constantValue!); +console.log(s0 === s.toBigInt()); // console.log(s.bits); From a9edabd908c2fd94786a7a8e83b71d7c55333f03 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 1 Jun 2023 13:44:17 +0200 Subject: [PATCH 09/27] fixup scalar representation --- src/lib/scalar.ts | 62 ++++++++++++------- src/lib/scalar.unit-test.ts | 39 +++++++++--- src/lib/signature.ts | 3 +- .../tests/verify-in-snark.unit-test.ts | 13 ++-- src/snarky.d.ts | 6 +- 5 files changed, 84 insertions(+), 39 deletions(-) diff --git a/src/lib/scalar.ts b/src/lib/scalar.ts index 760af2f77..a4cb88922 100644 --- a/src/lib/scalar.ts +++ b/src/lib/scalar.ts @@ -1,4 +1,4 @@ -import { Snarky, Provable, Bool } from '../snarky.js'; +import { Snarky, Provable, Bool, MlArray } from '../snarky.js'; import { Scalar as Fq } from '../provable/curve-bigint.js'; import { Field, FieldConst, FieldVar } from './field.js'; @@ -19,11 +19,11 @@ let oneHalf = Fq.inverse(2n)!; * Represents a {@link Scalar}. */ class Scalar { - bits: BoolVar[]; + value: MlArray; constantValue?: ScalarConst; - constructor(bits: BoolVar[], constantValue?: Fq) { - this.bits = bits; + constructor(bits: MlArray, constantValue?: Fq) { + this.value = bits; constantValue ??= toConstantScalar(bits); if (constantValue !== undefined) { this.constantValue = ScalarConst.fromBigint(constantValue); @@ -42,10 +42,17 @@ class Scalar { return new Scalar(bits, scalar); } + toConstant() { + if (this.constantValue !== undefined) return this; + let [, ...bits] = this.value; + let constBits = bits.map((b) => FieldVar.constant(Snarky.field.readVar(b))); + return new Scalar([0, ...constBits]); + } + /** * @deprecated use {@link Scalar.from} */ - static fromBigint(x: bigint) { + static fromBigInt(x: bigint) { return Scalar.from(x); } @@ -72,13 +79,18 @@ class Scalar { // operations on constant scalars - #assertConstant(name: string): Fq { - if (this.constantValue === undefined) + // TODO: this is a static method so that it works on ml scalars as well + static #assertConstantStatic(x: Scalar, name: string): Fq { + if (x.constantValue === undefined) throw Error( `Scalar.${name}() 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.` ); - return ScalarConst.toBigint(this.constantValue); + return ScalarConst.toBigint(x.constantValue); + } + + #assertConstant(name: string) { + return Scalar.#assertConstantStatic(this, name); } /** @@ -99,7 +111,7 @@ That means it can't be called in a @method or similar environment, and there's n */ add(y: Scalar) { let x = this.#assertConstant('add'); - let y0 = y.#assertConstant('add'); + let y0 = Scalar.#assertConstantStatic(y, 'add'); let z = Fq.add(x, y0); return Scalar.from(z); } @@ -111,7 +123,7 @@ That means it can't be called in a @method or similar environment, and there's n */ sub(y: Scalar) { let x = this.#assertConstant('sub'); - let y0 = y.#assertConstant('sub'); + let y0 = Scalar.#assertConstantStatic(y, 'sub'); let z = Fq.sub(x, y0); return Scalar.from(z); } @@ -123,7 +135,7 @@ That means it can't be called in a @method or similar environment, and there's n */ mul(y: Scalar) { let x = this.#assertConstant('mul'); - let y0 = y.#assertConstant('mul'); + let y0 = Scalar.#assertConstantStatic(y, 'mul'); let z = Fq.mul(x, y0); return Scalar.from(z); } @@ -136,7 +148,7 @@ That means it can't be called in a @method or similar environment, and there's n */ div(y: Scalar) { let x = this.#assertConstant('div'); - let y0 = y.#assertConstant('div'); + let y0 = Scalar.#assertConstantStatic(y, 'div'); let z = Fq.div(x, y0); if (z === undefined) throw Error('Scalar.div(): Division by zero'); return Scalar.from(z); @@ -174,7 +186,8 @@ That means it can't be called in a @method or similar environment, and there's n * The fields are not constrained to be boolean. */ static toFields(x: Scalar) { - return x.bits.map((b) => new Field(b)); + let [, ...bits] = x.value; + return bits.map((b) => new Field(b)); } /** @@ -206,7 +219,7 @@ That means it can't be called in a @method or similar environment, and there's n * Creates a data structure from an array of serialized {@link Field} elements. */ static fromFields(fields: Field[]): Scalar { - return new Scalar(fields.map((x) => x.value)); + return new Scalar([0, ...fields.map((x) => x.value)]); } /** @@ -246,15 +259,15 @@ That means it can't be called in a @method or similar environment, and there's n * This operation does _not_ affect the circuit and can't be used to prove anything about the string representation of the Scalar. */ static toJSON(x: Scalar) { - return x.toJSON(); + let s = Scalar.#assertConstantStatic(x, 'toJSON'); + return s.toString(); } /** * Serializes this Scalar to a string */ toJSON() { - let s = this.#assertConstant('toJSON'); - return s.toString(); + return Scalar.toJSON(this); } /** @@ -266,7 +279,7 @@ That means it can't be called in a @method or similar environment, and there's n } } -function toConstantScalar(bits: BoolVar[]): Fq | undefined { +function toConstantScalar([, ...bits]: MlArray): Fq | undefined { let constantBits = Array(bits.length); for (let i = 0; i < bits.length; i++) { let bool = bits[i]; @@ -274,13 +287,16 @@ function toConstantScalar(bits: BoolVar[]): Fq | undefined { constantBits[i] = FieldConst.equal(bool[1], FieldConst[1]); } let sShifted = Fq.fromBits(constantBits); - return unshift(sShifted); + return shift(sShifted); } -function toBits(constantValue: Fq): BoolVar[] { - return Fq.toBits(shift(constantValue)).map((b) => - FieldVar.constant(BigInt(b)) - ); +function toBits(constantValue: Fq): MlArray { + return [ + 0, + ...Fq.toBits(unshift(constantValue)).map((b) => + FieldVar.constant(BigInt(b)) + ), + ]; } /** diff --git a/src/lib/scalar.unit-test.ts b/src/lib/scalar.unit-test.ts index a2eada241..a32de1dcc 100644 --- a/src/lib/scalar.unit-test.ts +++ b/src/lib/scalar.unit-test.ts @@ -1,15 +1,40 @@ -import { Provable } from '../snarky.js'; import { Scalar as Fq } from '../provable/curve-bigint.js'; -import { FieldVar } from './field.js'; -import { Scalar, ScalarConst, shift, unshift } from './scalar.js'; +import { Field, FieldVar } from './field.js'; +import { Scalar, ScalarConst, shift } from './scalar.js'; +import { Provable } from './provable.js'; +import { MlArray, Scalar as ScalarSnarky } from '../snarky.js'; + +let scalarShift = Fq(1n + 2n ** 255n); +let oneHalf = Fq.inverse(2n)!; // types Scalar satisfies Provable; -let s0 = Fq.random(); -let bits = Fq.toBits(shift(s0)).map((b) => FieldVar.constant(BigInt(b))); +// let s0 = Fq.random(); +// let bits = Fq.toBits(shift(s0)).map((b) => FieldVar.constant(BigInt(b))); -let s = new Scalar(bits); +// let s = new Scalar(bits); -console.log(s0 === s.toBigInt()); +// console.log(s0 === s.toBigInt()); // console.log(s.bits); + +let h0 = new Field( + 14402037339225697224364383372940415733563435755837035364105242770332943848092n +); +let bits = h0.toBits(); +let bitsRaw = [ + 0, + ...bits.map((x) => x.toField().value), +] satisfies MlArray; +console.log(bits.length); +let h1 = new Scalar(bitsRaw); +let h2 = new ScalarSnarky(bitsRaw); +console.log(ScalarConst.toBigint(h1.constantValue!)); +console.log(ScalarConst.toBigint(h2.constantValue!)); + +let h3 = Scalar.fromFields(Scalar.toFields(h1)); +console.log(ScalarConst.toBigint(h3.constantValue!)); + +function printBits([, ...bits]: [0, ...[0, Uint8Array][]]) { + console.log(bits.map((b) => b[1][0]).join(' ')); +} diff --git a/src/lib/signature.ts b/src/lib/signature.ts index b5fe6e2c3..4c3d131d7 100644 --- a/src/lib/signature.ts +++ b/src/lib/signature.ts @@ -1,4 +1,4 @@ -import { Group, Bool, Scalar, Ledger } from '../snarky.js'; +import { Group, Bool, Ledger } from '../snarky.js'; import { Field } from './core.js'; import { prop, CircuitValue, AnyConstructor } from './circuit_value.js'; import { hashWithPrefix } from './hash.js'; @@ -8,6 +8,7 @@ import { } from '../mina-signer/src/signature.js'; import { Scalar as ScalarBigint } from '../provable/curve-bigint.js'; import { prefixes } from '../bindings/crypto/constants.js'; +import { Scalar } from './scalar.js'; // external API export { PrivateKey, PublicKey, Signature }; diff --git a/src/mina-signer/tests/verify-in-snark.unit-test.ts b/src/mina-signer/tests/verify-in-snark.unit-test.ts index ad4fb0b7a..62ce2b574 100644 --- a/src/mina-signer/tests/verify-in-snark.unit-test.ts +++ b/src/mina-signer/tests/verify-in-snark.unit-test.ts @@ -1,9 +1,8 @@ -import { isReady, shutdown } from '../../snarky.js'; +import { isReady } from '../../snarky.js'; import { Field } from '../../lib/core.js'; import { ZkProgram } from '../../lib/proof_system.js'; import Client from '../MinaSigner.js'; import { PrivateKey, Signature } from '../../lib/signature.js'; -import { provablePure } from '../../lib/circuit_value.js'; import { expect } from 'expect'; import { Provable } from '../../lib/provable.js'; @@ -28,17 +27,17 @@ expect(signatureSnarky.toBase58()).toEqual(signed.signature); // verify out-of-snark with snarkyjs let publicKey = privateKeySnarky.toPublicKey(); let signature = Signature.fromBase58(signed.signature); +Provable.assertEqual(Signature, signature, signatureSnarky); signature.verify(publicKey, fieldsSnarky).assertTrue(); // verify in-snark with snarkyjs const Message = Provable.Array(Field, fields.length); const MyProgram = ZkProgram({ - publicInput: provablePure(null), methods: { verifySignature: { privateInputs: [Signature, Message], - method(_: null, signature: Signature, message: Field[]) { + method(signature: Signature, message: Field[]) { signature.verify(publicKey, message).assertTrue(); }, }, @@ -46,7 +45,7 @@ const MyProgram = ZkProgram({ }); await MyProgram.compile(); -let proof = await MyProgram.verifySignature(null, signature, fieldsSnarky); +let proof = await MyProgram.verifySignature(signature, fieldsSnarky); ok = await MyProgram.verify(proof); expect(ok).toEqual(true); @@ -61,7 +60,7 @@ invalidSignature.verify(publicKey, fieldsSnarky).assertFalse(); // can't verify in snark await expect(() => - MyProgram.verifySignature(null, invalidSignature, fieldsSnarky) + MyProgram.verifySignature(invalidSignature, fieldsSnarky) ).rejects.toThrow('Constraint unsatisfied'); // negative test - try to verify a different message @@ -74,5 +73,5 @@ signature.verify(publicKey, wrongFields).assertFalse(); // can't verify in snark await expect(() => - MyProgram.verifySignature(null, signature, wrongFields) + MyProgram.verifySignature(signature, wrongFields) ).rejects.toThrow('Constraint unsatisfied'); diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 5b57ce985..4a325c673 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -17,7 +17,7 @@ export { }; // internal -export { Snarky, Test, JsonGate }; +export { Snarky, Test, JsonGate, MlArray }; /** * `Provable` is the general circuit type interface. Provable interface describes how a type `T` is made up of field elements and auxiliary (non-field element) data. @@ -1236,6 +1236,10 @@ type Gate = { * Represents a {@link Scalar}. */ declare class Scalar { + value: MlArray; + constantValue?: Uint8Array; + constructor(bits: MlArray, constantValue?: Uint8Array); + /** * Serialize this Scalar to Field elements. * From cb497c80bbb1f701640297f6043a75c7cf6269b9 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 1 Jun 2023 13:44:33 +0200 Subject: [PATCH 10/27] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 5ebeb11cd..a884c1ffa 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 5ebeb11cd217922eede2cdc37e655a5360b79819 +Subproject commit a884c1ffadd0fc06f0ae12ea2feba554e533d4eb From 80b090bc399fa4a52faefeee002657689b678279 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 1 Jun 2023 14:44:00 +0200 Subject: [PATCH 11/27] export new scalar everywhere --- src/lib/core.ts | 31 +-------- src/lib/encryption.ts | 4 +- src/lib/scalar.ts | 20 +++++- src/lib/scalar.unit-test.ts | 41 ++++------- src/lib/signature.ts | 3 +- .../src/sign-zkapp-command.unit-test.ts | 11 +-- src/snarky.d.ts | 68 +++++++++---------- 7 files changed, 76 insertions(+), 102 deletions(-) diff --git a/src/lib/core.ts b/src/lib/core.ts index 43e279bd7..6ebbb664f 100644 --- a/src/lib/core.ts +++ b/src/lib/core.ts @@ -1,11 +1,7 @@ -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, Group } from '../snarky.js'; +import { Bool, Group } from '../snarky.js'; import { Field as InternalField } from './field.js'; -import { Scalar as InternalScalar } from './scalar.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 }; @@ -67,7 +63,6 @@ type InferReturn = T extends new (...args: any) => infer Return // patching ocaml classes Bool.toAuxiliary = () => []; -Scalar.toAuxiliary = () => []; Group.toAuxiliary = () => []; Bool.toInput = function (x) { @@ -87,25 +82,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()); -}; diff --git a/src/lib/encryption.ts b/src/lib/encryption.ts index f32798d3a..a04ca1f0a 100644 --- a/src/lib/encryption.ts +++ b/src/lib/encryption.ts @@ -1,5 +1,5 @@ -import { Group, Scalar } from '../snarky.js'; -import { Field } from './core.js'; +import { Group } from '../snarky.js'; +import { Field, Scalar } from './core.js'; import { Poseidon } from './hash.js'; import { Provable } from './provable.js'; import { PrivateKey, PublicKey } from './signature.js'; diff --git a/src/lib/scalar.ts b/src/lib/scalar.ts index a4cb88922..c4e70a0d4 100644 --- a/src/lib/scalar.ts +++ b/src/lib/scalar.ts @@ -22,7 +22,9 @@ class Scalar { value: MlArray; constantValue?: ScalarConst; - constructor(bits: MlArray, constantValue?: Fq) { + static ORDER = Fq.modulus; + + private constructor(bits: MlArray, constantValue?: Fq) { this.value = bits; constantValue ??= toConstantScalar(bits); if (constantValue !== undefined) { @@ -60,6 +62,8 @@ class Scalar { return this.#assertConstant('toBigInt'); } + // TODO: fix this API. we should represent "shifted status" internally and use + // and use shifted Group.scale only if the scalar bits representation is shifted /** * Creates a data structure from an array of serialized {@link Bool}. * @@ -154,6 +158,16 @@ That means it can't be called in a @method or similar environment, and there's n return Scalar.from(z); } + shift() { + let x = this.#assertConstant('shift'); + return Scalar.from(shift(x)); + } + + unshift() { + let x = this.#assertConstant('unshift'); + return Scalar.from(unshift(x)); + } + /** * Serialize a Scalar into a Field element plus one bit, where the bit is represented as a Bool. * @@ -280,6 +294,10 @@ That means it can't be called in a @method or similar environment, and there's n } function toConstantScalar([, ...bits]: MlArray): Fq | undefined { + if (bits.length !== Fq.sizeInBits) + throw Error( + `Scalar: expected bits array of length ${Fq.sizeInBits}, got ${bits.length}` + ); let constantBits = Array(bits.length); for (let i = 0; i < bits.length; i++) { let bool = bits[i]; diff --git a/src/lib/scalar.unit-test.ts b/src/lib/scalar.unit-test.ts index a32de1dcc..aa8d9de62 100644 --- a/src/lib/scalar.unit-test.ts +++ b/src/lib/scalar.unit-test.ts @@ -1,40 +1,29 @@ import { Scalar as Fq } from '../provable/curve-bigint.js'; import { Field, FieldVar } from './field.js'; -import { Scalar, ScalarConst, shift } from './scalar.js'; +import { Scalar, shift, unshift } from './scalar.js'; import { Provable } from './provable.js'; -import { MlArray, Scalar as ScalarSnarky } from '../snarky.js'; +import { Bool, MlArray } from '../snarky.js'; +import assert from 'assert'; -let scalarShift = Fq(1n + 2n ** 255n); -let oneHalf = Fq.inverse(2n)!; - -// types Scalar satisfies Provable; -// let s0 = Fq.random(); -// let bits = Fq.toBits(shift(s0)).map((b) => FieldVar.constant(BigInt(b))); - -// let s = new Scalar(bits); +let x = Field.random().toBigInt(); -// console.log(s0 === s.toBigInt()); -// console.log(s.bits); - -let h0 = new Field( - 14402037339225697224364383372940415733563435755837035364105242770332943848092n -); +let h0 = new Field(unshift(x)); let bits = h0.toBits(); let bitsRaw = [ 0, ...bits.map((x) => x.toField().value), ] satisfies MlArray; -console.log(bits.length); -let h1 = new Scalar(bitsRaw); -let h2 = new ScalarSnarky(bitsRaw); -console.log(ScalarConst.toBigint(h1.constantValue!)); -console.log(ScalarConst.toBigint(h2.constantValue!)); -let h3 = Scalar.fromFields(Scalar.toFields(h1)); -console.log(ScalarConst.toBigint(h3.constantValue!)); +let h1 = new (Scalar as any)(bitsRaw); +let h2 = Scalar.fromFields(Scalar.toFields(h1)); +let h3 = Scalar.from(x); + +assert(h1.toBigInt() === x); +assert(h2.toBigInt() === x); +assert(h3.toBigInt() === x); -function printBits([, ...bits]: [0, ...[0, Uint8Array][]]) { - console.log(bits.map((b) => b[1][0]).join(' ')); -} +let bits_ = Fq.toBits(unshift(x)).map((b) => Bool(b)); +let s = Scalar.fromBits(bits_); +assert(x === s.toBigInt()); diff --git a/src/lib/signature.ts b/src/lib/signature.ts index 4c3d131d7..5c78b9459 100644 --- a/src/lib/signature.ts +++ b/src/lib/signature.ts @@ -1,5 +1,5 @@ import { Group, Bool, Ledger } from '../snarky.js'; -import { Field } from './core.js'; +import { Field, Scalar } from './core.js'; import { prop, CircuitValue, AnyConstructor } from './circuit_value.js'; import { hashWithPrefix } from './hash.js'; import { @@ -8,7 +8,6 @@ import { } from '../mina-signer/src/signature.js'; import { Scalar as ScalarBigint } from '../provable/curve-bigint.js'; import { prefixes } from '../bindings/crypto/constants.js'; -import { Scalar } from './scalar.js'; // external API export { PrivateKey, PublicKey, Signature }; diff --git a/src/mina-signer/src/sign-zkapp-command.unit-test.ts b/src/mina-signer/src/sign-zkapp-command.unit-test.ts index e6e5f8285..19df5600d 100644 --- a/src/mina-signer/src/sign-zkapp-command.unit-test.ts +++ b/src/mina-signer/src/sign-zkapp-command.unit-test.ts @@ -1,11 +1,6 @@ import { expect } from 'expect'; -import { - isReady, - Ledger, - Bool as BoolSnarky, - Scalar as ScalarSnarky, - shutdown, -} from '../../snarky.js'; +import { isReady, Ledger, shutdown } from '../../snarky.js'; +import { Scalar as ScalarSnarky } from '../../lib/core.js'; import { PrivateKey as PrivateKeySnarky, PublicKey as PublicKeySnarky, @@ -116,7 +111,7 @@ test(Random.accountUpdate, (accountUpdate) => { test(Random.json.privateKey, (feePayerKeyBase58) => { let feePayerKey = PrivateKey.fromBase58(feePayerKeyBase58); let feePayerKeySnarky = PrivateKeySnarky.fromBase58(feePayerKeyBase58); - let feePayerCompressed = ScalarSnarky.toFieldsCompressed(feePayerKeySnarky.s); + let feePayerCompressed = feePayerKeySnarky.s.toFieldsCompressed(); expect(feePayerKey).toEqual(feePayerCompressed.field.toBigInt()); expect(PrivateKey.toBase58(feePayerKey)).toEqual(feePayerKeyBase58); }); diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 4a325c673..a9827e39b 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -5,7 +5,7 @@ export { SnarkyField }; export { Bool, Group, - Scalar, + SnarkyScalar, ProvablePure, Provable, Poseidon, @@ -1233,9 +1233,9 @@ type Gate = { }; /** - * Represents a {@link Scalar}. + * Represents a {@link SnarkyScalar}. */ -declare class Scalar { +declare class SnarkyScalar { value: MlArray; constantValue?: Uint8Array; constructor(bits: MlArray, constantValue?: Uint8Array); @@ -1245,7 +1245,7 @@ declare class Scalar { * * WARNING: This function is for internal usage by the proof system. It returns 255 field elements * which represent the Scalar in a shifted, bitwise format. - * Check out {@link Scalar.toFieldsCompressed} for a user-friendly serialization that can be used outside proofs. + * Check out {@link SnarkyScalar.toFieldsCompressed} for a user-friendly serialization that can be used outside proofs. */ toFields(): Field[]; @@ -1255,37 +1255,37 @@ declare class Scalar { * Note: Since the Scalar field is slightly larger than the base Field, an additional high bit * is needed to represent all Scalars. However, for a random Scalar, the high bit will be `false` with overwhelming probability. */ - static toFieldsCompressed(s: Scalar): { field: Field; highBit: Bool }; + static toFieldsCompressed(s: SnarkyScalar): { field: Field; highBit: Bool }; /** * Negate a scalar field element. * Can only be called outside of circuit execution */ - neg(): Scalar; + neg(): SnarkyScalar; /** * Add scalar field elements. * Can only be called outside of circuit execution */ - add(y: Scalar): Scalar; + add(y: SnarkyScalar): SnarkyScalar; /** * Subtract scalar field elements. * Can only be called outside of circuit execution */ - sub(y: Scalar): Scalar; + sub(y: SnarkyScalar): SnarkyScalar; /** * Multiply scalar field elements. * Can only be called outside of circuit execution */ - mul(y: Scalar): Scalar; + mul(y: SnarkyScalar): SnarkyScalar; /** * Divide scalar field elements. * Can only be called outside of circuit execution */ - div(y: Scalar): Scalar; + div(y: SnarkyScalar): SnarkyScalar; /** * Serializes this Scalar to a string @@ -1293,17 +1293,17 @@ declare class Scalar { toJSON(): string; /** - * Static method to serialize a {@link Scalar} into an array of {@link Field} elements. + * Static method to serialize a {@link SnarkyScalar} into an array of {@link Field} elements. */ - static toFields(x: Scalar): Field[]; + static toFields(x: SnarkyScalar): Field[]; /** - * Static method to serialize a {@link Scalar} into its auxiliary data. + * Static method to serialize a {@link SnarkyScalar} into its auxiliary data. */ - static toAuxiliary(x?: Scalar): []; + static toAuxiliary(x?: SnarkyScalar): []; /** * Creates a data structure from an array of serialized {@link Field} elements. */ - static fromFields(fields: Field[]): Scalar; + static fromFields(fields: Field[]): SnarkyScalar; /** * Returns the size of this type. */ @@ -1311,28 +1311,28 @@ declare class Scalar { /** * Creates a data structure from an array of serialized {@link Bool}. */ - static fromBits(bits: Bool[]): Scalar; + static fromBits(bits: Bool[]): SnarkyScalar; /** - * Returns a random {@link Scalar}. + * Returns a random {@link SnarkyScalar}. * Randomness can not be proven inside a circuit! */ - static random(): Scalar; + static random(): SnarkyScalar; /** - * Serialize a {@link Scalar} to a JSON string. + * Serialize a {@link SnarkyScalar} to a JSON string. * This operation does _not_ affect the circuit and can't be used to prove anything about the string representation of the Scalar. */ - static toJSON(x: Scalar): string; + static toJSON(x: SnarkyScalar): string; /** - * Deserialize a JSON structure into a {@link Scalar}. + * Deserialize a JSON structure into a {@link SnarkyScalar}. * This operation does _not_ affect the circuit and can't be used to prove anything about the string representation of the Scalar. */ - static fromJSON(x: string | number | boolean): Scalar; + static fromJSON(x: string | number | boolean): SnarkyScalar; /** - * Create a constant {@link Scalar} from a bigint. + * Create a constant {@link SnarkyScalar} from a bigint. * If the bigint is too large, it is reduced modulo the scalar field order. */ - static fromBigInt(s: bigint): Scalar; - static check(x: Scalar): void; + static fromBigInt(s: bigint): SnarkyScalar; + static check(x: SnarkyScalar): void; } // TODO: Add this when OCaml bindings are implemented: @@ -1365,9 +1365,9 @@ declare class Group { neg(): Group; /** - * Scales this {@link Group} element using a {@link Scalar}. + * Scales this {@link Group} element using a {@link SnarkyScalar}. */ - scale(y: Scalar): Group; + scale(y: SnarkyScalar): Group; // TODO: Add this function when OCaml bindings are implemented : endoScale(y: EndoScalar): Group; /** @@ -1409,9 +1409,9 @@ declare class Group { static neg(x: Group): Group; /** - * Scales this {@link Group} element using a {@link Scalar}. + * Scales this {@link Group} element using a {@link SnarkyScalar}. */ - static scale(x: Group, y: Scalar): Group; + static scale(x: Group, y: SnarkyScalar): Group; // TODO: Add this function when OCaml bindings are implemented : static endoScale(x: Group, y: EndoScalar): Group; /** @@ -1542,7 +1542,7 @@ declare class Ledger { */ static signFieldElement( messageHash: Field, - privateKey: { s: Scalar }, + privateKey: { s: SnarkyScalar }, isMainnet: boolean ): string; @@ -1554,14 +1554,14 @@ declare class Ledger { /** * Signs a transaction as the fee payer. */ - static signFeePayer(txJson: string, privateKey: { s: Scalar }): string; + static signFeePayer(txJson: string, privateKey: { s: SnarkyScalar }): string; /** * Signs an account update. */ static signOtherAccountUpdate( txJson: string, - privateKey: { s: Scalar }, + privateKey: { s: SnarkyScalar }, i: number ): string; @@ -1571,8 +1571,8 @@ declare class Ledger { static publicKeyToString(publicKey: PublicKey_): string; static publicKeyOfString(publicKeyBase58: string): PublicKey_; - static privateKeyToString(privateKey: { s: Scalar }): string; - static privateKeyOfString(privateKeyBase58: string): Scalar; + static privateKeyToString(privateKey: { s: SnarkyScalar }): string; + static privateKeyOfString(privateKeyBase58: string): SnarkyScalar; static fieldToBase58(field: Field): string; static fieldOfBase58(fieldBase58: string): Field; From effad740dd66eb45f168d68c6aadba9c8e2538b1 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 1 Jun 2023 14:44:09 +0200 Subject: [PATCH 12/27] update tests --- src/lib/group.test.ts | 16 ++--- src/lib/scalar.test.ts | 148 ++++++++++++++++------------------------- 2 files changed, 67 insertions(+), 97 deletions(-) diff --git a/src/lib/group.test.ts b/src/lib/group.test.ts index c7ed675c2..0ffa0fd8b 100644 --- a/src/lib/group.test.ts +++ b/src/lib/group.test.ts @@ -70,8 +70,8 @@ describe('group', () => { expect(() => { Provable.runAndCheck(() => { const g = new Group(1, 1); - 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); @@ -83,8 +83,8 @@ describe('group', () => { expect(() => { Provable.runAndCheck(() => { const g = new Group(1, 1); - 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); @@ -241,8 +241,8 @@ describe('group', () => { it('x*g+y*g = (x+y)*g', () => { const g = new Group(1, 1); - 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); @@ -250,8 +250,8 @@ describe('group', () => { it('x*(y*g) = (x*y)*g', () => { const g = new Group(1, 1); - 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); diff --git a/src/lib/scalar.test.ts b/src/lib/scalar.test.ts index 4a3e4e94b..d3e80a378 100644 --- a/src/lib/scalar.test.ts +++ b/src/lib/scalar.test.ts @@ -25,11 +25,13 @@ describe('scalar', () => { }); }); - describe('fromFields', () => { - it('should return a Scalar', () => { + describe('toFields / fromFields', () => { + it('should return the same', () => { expect(() => { + let s0 = Scalar.random(); Provable.runAndCheck(() => { - Provable.witness(Scalar, () => Scalar.fromFields([Field(1)])); + let s1 = Provable.witness(Scalar, () => s0); + Provable.assertEqual(Scalar.fromFields(s1.toFields()), s0); }); }).not.toThrow(); }); @@ -39,21 +41,15 @@ describe('scalar', () => { it('should return a Scalar', () => { expect(() => { Provable.runAndCheck(() => { - Provable.witness(Scalar, () => Scalar.fromBits([Bool(true)])); + Provable.witness(Scalar, () => + Scalar.fromBits(Field.random().toBits()) + ); }); }).not.toThrow(); }); }); describe('random', () => { - it('should not crash', () => { - expect(() => { - Provable.runAndCheck(() => { - Provable.witness(Scalar, () => Scalar.random()); - }); - }).not.toThrow(); - }); - it('two different calls should be different', () => { Provable.runAndCheck(() => { const x = Provable.witness(Scalar, () => Scalar.random()); @@ -65,39 +61,23 @@ describe('scalar', () => { }); describe('Outside circuit', () => { - describe('toFields', () => { - it('should return an array of Fields', () => { - expect(() => { - const x = Scalar.random(); - const fieldArr = x.toFields(); - expect(Array.isArray(fieldArr)).toBe(true); - }).not.toThrow(); - }); - }); - - describe('fromFields', () => { - it('should return a Scalar', () => { - expect(() => { - Scalar.fromFields([Field(1)]); - }).not.toThrow(); + describe('toFields / fromFields', () => { + it('roundtrip works', () => { + let x = Scalar.random(); + expect(Scalar.fromFields(x.toFields())).toEqual(x); }); }); describe('fromBits', () => { - it('should return a Scalar', () => { - expect(() => { - Scalar.fromBits([Bool(true)]); - }).not.toThrow(); + it('should return a shifted scalar', () => { + let x = Field.random(); + let bits_ = x.toBits(); + let s = Scalar.fromBits(bits_).unshift(); + expect(x.toBigInt()).toEqual(s.toBigInt()); }); }); describe('random', () => { - it('should not crash', () => { - expect(() => { - Scalar.random(); - }).not.toThrow(); - }); - it('two different calls should be different', () => { expect(Scalar.random()).not.toEqual(Scalar.random()); }); @@ -115,138 +95,128 @@ describe('scalar', () => { }); it('fromJSON(1) should be 1', () => { - expect(Scalar.fromJSON(1)!.toJSON()).toEqual('1'); + expect(Scalar.from(1).toJSON()).toEqual('1'); }); - it('fromJSON(true) should be 1', () => { - expect(Scalar.fromJSON(true)!.toJSON()).toEqual('1'); - }); - - it('fromJSON(false) should be 0', () => { - expect(Scalar.fromJSON(false)!.toJSON()).toEqual('0'); + it('fromJSON(0n) should be 1', () => { + expect(Scalar.from(0n).toJSON()).toEqual('0'); }); }); describe('neg', () => { it('neg(1)=-1', () => { - const x = Scalar.fromJSON(1)!; - expect(x.neg().toJSON()).toEqual( - '28948022309329048855892746252171976963363056481941647379679742748393362948096' - ); + const x = Scalar.from(1); + expect(x.neg().toBigInt()).toEqual(Scalar.ORDER - 1n); }); it('neg(-1)=1', () => { - const x = Scalar.fromJSON(-1)!; - expect(x.neg().toJSON()).toEqual( - '28948022309329048855892746252171976963363056481941647379661296004319653396482' - ); + const x = Scalar.from(-1); + expect(x.neg().toJSON()).toEqual('1'); }); it('neg(0)=0', () => { - const x = Scalar.fromJSON(0)!; + const x = Scalar.from(0); expect(x.neg().toJSON()).toEqual('0'); }); }); describe('add', () => { it('1+1=2', () => { - const x = Scalar.fromJSON(1)!; - const y = Scalar.fromJSON(1)!; + const x = Scalar.from(1); + const y = Scalar.from(1); expect(x.add(y).toJSON()).toEqual('2'); }); it('5000+5000=10000', () => { - const x = Scalar.fromJSON(5000)!; - const y = Scalar.fromJSON(5000)!; + const x = Scalar.from(5000); + const y = Scalar.from(5000); expect(x.add(y).toJSON()).toEqual('10000'); }); it('((2^64/2)+(2^64/2)) adds to 2^64', () => { const v = (1n << 64n) - 2n; - const x = Scalar.fromJSON(String(v / 2n))!; - const y = Scalar.fromJSON(String(v / 2n))!; + const x = Scalar.fromJSON(String(v / 2n)); + const y = Scalar.fromJSON(String(v / 2n)); expect(x.add(y).toJSON()).toEqual(String(v)); }); }); describe('sub', () => { it('1-1=0', () => { - const x = Scalar.fromJSON(1)!; - const y = Scalar.fromJSON(1)!; + const x = Scalar.from(1); + const y = Scalar.from(1); expect(x.sub(y).toJSON()).toEqual('0'); }); it('10000-5000=5000', () => { - const x = Scalar.fromJSON(10000)!; - const y = Scalar.fromJSON(5000)!; + const x = Scalar.from(10000); + const y = Scalar.from(5000); expect(x.sub(y).toJSON()).toEqual('5000'); }); - // Expected: "-1" Received: "28948022309329048855892746252171976963363056481941647379679742748393362948096" - it.skip('0-1=-1', () => { - const x = Scalar.fromJSON(0)!; - const y = Scalar.fromJSON(1)!; - expect(x.sub(y).toJSON()).toEqual('-1'); + it('0-1=-1', () => { + const x = Scalar.from(0); + const y = Scalar.from(1); + expect(x.sub(y).toBigInt()).toEqual(Scalar.ORDER - 1n); }); - // Expected: "2" Received: "28948022309329048855892746252171976963363056481941647379661296004319653396483" - it.skip('1-(-1)=2', () => { - const x = Scalar.fromJSON(1)!; - const y = Scalar.fromJSON(-1)!; - expect(x.sub(y).toJSON()).toEqual('2'); + it('1-(-1)=2', () => { + const x = Scalar.from(1); + const y = Scalar.from(-1); + expect(x.sub(y).toBigInt()).toEqual(2n); }); }); describe('mul', () => { it('1x2=2', () => { - const x = Scalar.fromJSON(1)!; - const y = Scalar.fromJSON(2)!; + const x = Scalar.from(1); + const y = Scalar.from(2); expect(x.mul(y).toJSON()).toEqual('2'); }); it('1x0=0', () => { - const x = Scalar.fromJSON(1)!; - const y = Scalar.fromJSON(0)!; + const x = Scalar.from(1); + const y = Scalar.from(0); expect(x.mul(y).toJSON()).toEqual('0'); }); it('1000x1000=1000000', () => { - const x = Scalar.fromJSON(1000)!; - const y = Scalar.fromJSON(1000)!; + const x = Scalar.from(1000); + const y = Scalar.from(1000); expect(x.mul(y).toJSON()).toEqual('1000000'); }); it('(2^64-1)x1=(2^64-1)', () => { const v = (1n << 64n) - 1n; - const x = Scalar.fromJSON(String(v))!; - const y = Scalar.fromJSON(1)!; + const x = Scalar.from(String(v)); + const y = Scalar.from(1); expect(x.mul(y).toJSON()).toEqual(String(v)); }); }); describe('div', () => { it('2/1=2', () => { - const x = Scalar.fromJSON(2)!; - const y = Scalar.fromJSON(1)!; + const x = Scalar.from(2); + const y = Scalar.from(1); expect(x.div(y).toJSON()).toEqual('2'); }); it('0/1=0', () => { - const x = Scalar.fromJSON(0)!; - const y = Scalar.fromJSON(1)!; + const x = Scalar.from(0); + const y = Scalar.from(1); expect(x.div(y).toJSON()).toEqual('0'); }); it('2000/1000=2', () => { - const x = Scalar.fromJSON(2000)!; - const y = Scalar.fromJSON(1000)!; + const x = Scalar.from(2000); + const y = Scalar.from(1000); expect(x.div(y).toJSON()).toEqual('2'); }); it('(2^64-1)/1=(2^64-1)', () => { const v = (1n << 64n) - 1n; - const x = Scalar.fromJSON(String(v))!; - const y = Scalar.fromJSON(1)!; + const x = Scalar.from(String(v)); + const y = Scalar.from(1); expect(x.div(y).toJSON()).toEqual(String(v)); }); }); From 90b7f7e34c64232072c0a1dda2fd7f9506ec0695 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 1 Jun 2023 14:44:28 +0200 Subject: [PATCH 13/27] remove new test --- src/lib/scalar.unit-test.ts | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 src/lib/scalar.unit-test.ts diff --git a/src/lib/scalar.unit-test.ts b/src/lib/scalar.unit-test.ts deleted file mode 100644 index aa8d9de62..000000000 --- a/src/lib/scalar.unit-test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Scalar as Fq } from '../provable/curve-bigint.js'; -import { Field, FieldVar } from './field.js'; -import { Scalar, shift, unshift } from './scalar.js'; -import { Provable } from './provable.js'; -import { Bool, MlArray } from '../snarky.js'; -import assert from 'assert'; - -Scalar satisfies Provable; - -let x = Field.random().toBigInt(); - -let h0 = new Field(unshift(x)); -let bits = h0.toBits(); -let bitsRaw = [ - 0, - ...bits.map((x) => x.toField().value), -] satisfies MlArray; - -let h1 = new (Scalar as any)(bitsRaw); -let h2 = Scalar.fromFields(Scalar.toFields(h1)); -let h3 = Scalar.from(x); - -assert(h1.toBigInt() === x); -assert(h2.toBigInt() === x); -assert(h3.toBigInt() === x); - -let bits_ = Fq.toBits(unshift(x)).map((b) => Bool(b)); -let s = Scalar.fromBits(bits_); -assert(x === s.toBigInt()); From a286394f30da1a591ad8456e7408881a2bac614a Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 1 Jun 2023 14:46:25 +0200 Subject: [PATCH 14/27] remove old scalar --- src/snarky.d.ts | 123 ++++-------------------------------------------- 1 file changed, 10 insertions(+), 113 deletions(-) diff --git a/src/snarky.d.ts b/src/snarky.d.ts index a9827e39b..f4c39f2d4 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -1,11 +1,11 @@ import type { Account as JsonAccount } from './bindings/mina-transaction/gen/transaction-json.js'; import type { Field, FieldConst, FieldVar } from './lib/field.js'; +import type { Scalar } from './lib/scalar.js'; // export { Field }; export { SnarkyField }; export { Bool, Group, - SnarkyScalar, ProvablePure, Provable, Poseidon, @@ -1232,109 +1232,6 @@ type Gate = { coeffs: string[]; }; -/** - * Represents a {@link SnarkyScalar}. - */ -declare class SnarkyScalar { - value: MlArray; - constantValue?: Uint8Array; - constructor(bits: MlArray, constantValue?: Uint8Array); - - /** - * Serialize this Scalar to Field elements. - * - * WARNING: This function is for internal usage by the proof system. It returns 255 field elements - * which represent the Scalar in a shifted, bitwise format. - * Check out {@link SnarkyScalar.toFieldsCompressed} for a user-friendly serialization that can be used outside proofs. - */ - toFields(): Field[]; - - /** - * Serialize a Scalar into a Field element plus one bit, where the bit is represented as a Bool. - * - * Note: Since the Scalar field is slightly larger than the base Field, an additional high bit - * is needed to represent all Scalars. However, for a random Scalar, the high bit will be `false` with overwhelming probability. - */ - static toFieldsCompressed(s: SnarkyScalar): { field: Field; highBit: Bool }; - - /** - * Negate a scalar field element. - * Can only be called outside of circuit execution - */ - neg(): SnarkyScalar; - - /** - * Add scalar field elements. - * Can only be called outside of circuit execution - */ - add(y: SnarkyScalar): SnarkyScalar; - - /** - * Subtract scalar field elements. - * Can only be called outside of circuit execution - */ - sub(y: SnarkyScalar): SnarkyScalar; - - /** - * Multiply scalar field elements. - * Can only be called outside of circuit execution - */ - mul(y: SnarkyScalar): SnarkyScalar; - - /** - * Divide scalar field elements. - * Can only be called outside of circuit execution - */ - div(y: SnarkyScalar): SnarkyScalar; - - /** - * Serializes this Scalar to a string - */ - toJSON(): string; - - /** - * Static method to serialize a {@link SnarkyScalar} into an array of {@link Field} elements. - */ - static toFields(x: SnarkyScalar): Field[]; - /** - * Static method to serialize a {@link SnarkyScalar} into its auxiliary data. - */ - static toAuxiliary(x?: SnarkyScalar): []; - /** - * Creates a data structure from an array of serialized {@link Field} elements. - */ - static fromFields(fields: Field[]): SnarkyScalar; - /** - * Returns the size of this type. - */ - static sizeInFields(): number; - /** - * Creates a data structure from an array of serialized {@link Bool}. - */ - static fromBits(bits: Bool[]): SnarkyScalar; - /** - * Returns a random {@link SnarkyScalar}. - * Randomness can not be proven inside a circuit! - */ - static random(): SnarkyScalar; - /** - * Serialize a {@link SnarkyScalar} to a JSON string. - * This operation does _not_ affect the circuit and can't be used to prove anything about the string representation of the Scalar. - */ - static toJSON(x: SnarkyScalar): string; - /** - * Deserialize a JSON structure into a {@link SnarkyScalar}. - * This operation does _not_ affect the circuit and can't be used to prove anything about the string representation of the Scalar. - */ - static fromJSON(x: string | number | boolean): SnarkyScalar; - /** - * Create a constant {@link SnarkyScalar} from a bigint. - * If the bigint is too large, it is reduced modulo the scalar field order. - */ - static fromBigInt(s: bigint): SnarkyScalar; - static check(x: SnarkyScalar): void; -} - // TODO: Add this when OCaml bindings are implemented: // declare class EndoScalar { // static toFields(x: Scalar): Field[]; @@ -1365,9 +1262,9 @@ declare class Group { neg(): Group; /** - * Scales this {@link Group} element using a {@link SnarkyScalar}. + * Scales this {@link Group} element using a {@link Scalar}. */ - scale(y: SnarkyScalar): Group; + scale(y: Scalar): Group; // TODO: Add this function when OCaml bindings are implemented : endoScale(y: EndoScalar): Group; /** @@ -1409,9 +1306,9 @@ declare class Group { static neg(x: Group): Group; /** - * Scales this {@link Group} element using a {@link SnarkyScalar}. + * Scales this {@link Group} element using a {@link Scalar}. */ - static scale(x: Group, y: SnarkyScalar): Group; + static scale(x: Group, y: Scalar): Group; // TODO: Add this function when OCaml bindings are implemented : static endoScale(x: Group, y: EndoScalar): Group; /** @@ -1542,7 +1439,7 @@ declare class Ledger { */ static signFieldElement( messageHash: Field, - privateKey: { s: SnarkyScalar }, + privateKey: { s: Scalar }, isMainnet: boolean ): string; @@ -1554,14 +1451,14 @@ declare class Ledger { /** * Signs a transaction as the fee payer. */ - static signFeePayer(txJson: string, privateKey: { s: SnarkyScalar }): string; + static signFeePayer(txJson: string, privateKey: { s: Scalar }): string; /** * Signs an account update. */ static signOtherAccountUpdate( txJson: string, - privateKey: { s: SnarkyScalar }, + privateKey: { s: Scalar }, i: number ): string; @@ -1571,8 +1468,8 @@ declare class Ledger { static publicKeyToString(publicKey: PublicKey_): string; static publicKeyOfString(publicKeyBase58: string): PublicKey_; - static privateKeyToString(privateKey: { s: SnarkyScalar }): string; - static privateKeyOfString(privateKeyBase58: string): SnarkyScalar; + static privateKeyToString(privateKey: { s: Scalar }): string; + static privateKeyOfString(privateKeyBase58: string): Scalar; static fieldToBase58(field: Field): string; static fieldOfBase58(fieldBase58: string): Field; From 0d3c3e8f4516c71da7ababae288bae726c9d0ff8 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 1 Jun 2023 15:04:06 +0200 Subject: [PATCH 15/27] remove old scalar class --- src/lib/scalar.ts | 3 ++- src/lib/signature.ts | 6 +++++- src/snarky.d.ts | 4 ++-- src/snarky.js | 3 +-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/lib/scalar.ts b/src/lib/scalar.ts index c4e70a0d4..078cad224 100644 --- a/src/lib/scalar.ts +++ b/src/lib/scalar.ts @@ -37,8 +37,9 @@ class Scalar { * * If the input is too large, it is reduced modulo the scalar field size. */ - static from(x: Scalar | bigint | number | string) { + static from(x: Scalar | Uint8Array | bigint | number | string) { if (x instanceof Scalar) return x; + if (x instanceof Uint8Array) x = ScalarConst.toBigint(x); let scalar = Fq(x); let bits = toBits(scalar); return new Scalar(bits, scalar); diff --git a/src/lib/signature.ts b/src/lib/signature.ts index 5c78b9459..c855f3cec 100644 --- a/src/lib/signature.ts +++ b/src/lib/signature.ts @@ -18,6 +18,10 @@ export { PrivateKey, PublicKey, Signature }; class PrivateKey extends CircuitValue { @prop s: Scalar; + constructor(s: Scalar) { + super(s); + } + /** * You can use this method to generate a private key. You can then obtain * the associated public key via {@link toPublicKey}. And generate signatures @@ -55,7 +59,7 @@ class PrivateKey extends CircuitValue { */ static fromBase58(privateKeyBase58: string) { let scalar = Ledger.privateKeyOfString(privateKeyBase58); - return new PrivateKey(scalar); + return new PrivateKey(Scalar.from(scalar)); } /** diff --git a/src/snarky.d.ts b/src/snarky.d.ts index f4c39f2d4..190845e15 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -1,6 +1,6 @@ import type { Account as JsonAccount } from './bindings/mina-transaction/gen/transaction-json.js'; import type { Field, FieldConst, FieldVar } from './lib/field.js'; -import type { Scalar } from './lib/scalar.js'; +import type { Scalar, ScalarConst } from './lib/scalar.js'; // export { Field }; export { SnarkyField }; export { @@ -1469,7 +1469,7 @@ declare class Ledger { static publicKeyToString(publicKey: PublicKey_): string; static publicKeyOfString(publicKeyBase58: string): PublicKey_; static privateKeyToString(privateKey: { s: Scalar }): string; - static privateKeyOfString(privateKeyBase58: string): Scalar; + static privateKeyOfString(privateKeyBase58: string): ScalarConst; static fieldToBase58(field: Field): string; static fieldOfBase58(fieldBase58: string): Field; diff --git a/src/snarky.js b/src/snarky.js index 4ed36d672..fed68a2c7 100644 --- a/src/snarky.js +++ b/src/snarky.js @@ -8,7 +8,6 @@ export { Snarky, Poseidon, Group, - Scalar, Ledger, shutdown, isReady, @@ -22,5 +21,5 @@ let isItReady = () => isReadyBoolean; function shutdown() {} -let { Field, Bool, Snarky, Poseidon, Group, Scalar, Ledger, Pickles, Test } = +let { Field, Bool, Snarky, Poseidon, Group, Ledger, Pickles, Test } = proxyClasses(getSnarky, isItReady, snarkySpec); From 156621c4e7395841dff8062866997ee1bd89455b Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 1 Jun 2023 15:42:21 +0200 Subject: [PATCH 16/27] move private key conversions & base58 to js --- src/lib/account_update.ts | 22 ++++++++++---- src/lib/ml/conversion.ts | 29 +++++++++++++++++++ src/lib/scalar.ts | 8 +++-- src/lib/signature.ts | 9 ++++-- .../src/sign-zkapp-command.unit-test.ts | 5 ++-- src/mina-signer/src/signature.unit-test.ts | 13 +++++++-- src/snarky.d.ts | 8 ++--- 7 files changed, 74 insertions(+), 20 deletions(-) create mode 100644 src/lib/ml/conversion.ts diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 8c6de8531..52c8d22ec 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -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 }; @@ -1870,11 +1871,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 }; } @@ -1894,7 +1899,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]; } @@ -1903,7 +1910,7 @@ function addMissingSignatures( : commitment; let signature = Ledger.signFieldElement( transactionCommitment, - privateKey, + Ml.fromPrivateKey(privateKey), false ); Authorization.setSignature(accountUpdate, signature); @@ -2059,7 +2066,10 @@ function signJsonTransaction( let feePayer = zkappCommand.feePayer; if (feePayer.body.publicKey === publicKey) { zkappCommand = JSON.parse( - Ledger.signFeePayer(JSON.stringify(zkappCommand), privateKey) + Ledger.signFeePayer( + JSON.stringify(zkappCommand), + Ml.fromPrivateKey(privateKey) + ) ); } for (let i = 0; i < zkappCommand.accountUpdates.length; i++) { @@ -2071,7 +2081,7 @@ function signJsonTransaction( zkappCommand = JSON.parse( Ledger.signOtherAccountUpdate( JSON.stringify(zkappCommand), - privateKey, + Ml.fromPrivateKey(privateKey), i ) ); diff --git a/src/lib/ml/conversion.ts b/src/lib/ml/conversion.ts new file mode 100644 index 000000000..fbe6f3919 --- /dev/null +++ b/src/lib/ml/conversion.ts @@ -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)); +} diff --git a/src/lib/scalar.ts b/src/lib/scalar.ts index 078cad224..f540903f7 100644 --- a/src/lib/scalar.ts +++ b/src/lib/scalar.ts @@ -15,6 +15,8 @@ const ScalarConst = { let scalarShift = Fq(1n + 2n ** 255n); let oneHalf = Fq.inverse(2n)!; +type ConstantScalar = Scalar & { constantValue: ScalarConst }; + /** * Represents a {@link Scalar}. */ @@ -45,11 +47,11 @@ class Scalar { return new Scalar(bits, scalar); } - toConstant() { - if (this.constantValue !== undefined) return this; + toConstant(): ConstantScalar { + if (this.constantValue !== undefined) return this as ConstantScalar; let [, ...bits] = this.value; let constBits = bits.map((b) => FieldVar.constant(Snarky.field.readVar(b))); - return new Scalar([0, ...constBits]); + return new Scalar([0, ...constBits]) as ConstantScalar; } /** diff --git a/src/lib/signature.ts b/src/lib/signature.ts index c855f3cec..1b8d6ec9f 100644 --- a/src/lib/signature.ts +++ b/src/lib/signature.ts @@ -6,7 +6,10 @@ import { deriveNonce, Signature as SignatureBigint, } from '../mina-signer/src/signature.js'; -import { Scalar as ScalarBigint } from '../provable/curve-bigint.js'; +import { + Scalar as ScalarBigint, + PrivateKey as PrivateKeyBigint, +} from '../provable/curve-bigint.js'; import { prefixes } from '../bindings/crypto/constants.js'; // external API @@ -58,7 +61,7 @@ class PrivateKey extends CircuitValue { * @returns a {@link PrivateKey}. */ static fromBase58(privateKeyBase58: string) { - let scalar = Ledger.privateKeyOfString(privateKeyBase58); + let scalar = PrivateKeyBigint.fromBase58(privateKeyBase58); return new PrivateKey(Scalar.from(scalar)); } @@ -76,7 +79,7 @@ class PrivateKey extends CircuitValue { * @returns a base58 encoded string */ static toBase58(privateKey: { s: Scalar }) { - return Ledger.privateKeyToString(privateKey); + return PrivateKeyBigint.toBase58(privateKey.s.toBigInt()); } } diff --git a/src/mina-signer/src/sign-zkapp-command.unit-test.ts b/src/mina-signer/src/sign-zkapp-command.unit-test.ts index 19df5600d..ab5d81a4b 100644 --- a/src/mina-signer/src/sign-zkapp-command.unit-test.ts +++ b/src/mina-signer/src/sign-zkapp-command.unit-test.ts @@ -43,6 +43,7 @@ import { import { Random, test, withHardCoded } from '../../lib/testing/property.js'; import { RandomTransaction } from './random-transaction.js'; import { Pickles } from '../../snarky.js'; +import { Ml } from '../../lib/ml/conversion.js'; // monkey-patch bigint to json (BigInt.prototype as any).toJSON = function () { @@ -216,12 +217,12 @@ test( let sigMainnet = signFieldElement(fullCommitment, feePayerKey, 'mainnet'); let sigTestnetOcaml = Ledger.signFieldElement( ocamlCommitments.fullCommitment, - feePayerKeySnarky, + Ml.fromPrivateKey(feePayerKeySnarky), false ); let sigMainnetOcaml = Ledger.signFieldElement( ocamlCommitments.fullCommitment, - feePayerKeySnarky, + Ml.fromPrivateKey(feePayerKeySnarky), true ); expect(Signature.toBase58(sigTestnet)).toEqual(sigTestnetOcaml); diff --git a/src/mina-signer/src/signature.unit-test.ts b/src/mina-signer/src/signature.unit-test.ts index b9fbe7474..dd4426719 100644 --- a/src/mina-signer/src/signature.unit-test.ts +++ b/src/mina-signer/src/signature.unit-test.ts @@ -15,6 +15,7 @@ import { PrivateKey as PrivateKeySnarky } from '../../lib/signature.js'; import { p } from '../../bindings/crypto/finite_field.js'; import { AccountUpdate } from '../../bindings/mina-transaction/gen/transaction-bigint.js'; import { HashInput } from '../../bindings/lib/provable-bigint.js'; +import { Ml } from '../../lib/ml/conversion.js'; await isReady; @@ -37,8 +38,16 @@ function checkConsistentSingle( expect(okTestnetMainnet).toEqual(false); expect(okMainnetMainnet).toEqual(true); // consistent with OCaml - let actualTest = Ledger.signFieldElement(FieldSnarky(msg), keySnarky, false); - let actualMain = Ledger.signFieldElement(FieldSnarky(msg), keySnarky, true); + let actualTest = Ledger.signFieldElement( + FieldSnarky(msg), + Ml.fromPrivateKey(keySnarky), + false + ); + let actualMain = Ledger.signFieldElement( + FieldSnarky(msg), + Ml.fromPrivateKey(keySnarky), + true + ); expect(Signature.toBase58(sigTest)).toEqual(actualTest); expect(Signature.toBase58(sigMain)).toEqual(actualMain); } diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 190845e15..04462ca6b 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -1439,7 +1439,7 @@ declare class Ledger { */ static signFieldElement( messageHash: Field, - privateKey: { s: Scalar }, + privateKey: ScalarConst, isMainnet: boolean ): string; @@ -1451,14 +1451,14 @@ declare class Ledger { /** * Signs a transaction as the fee payer. */ - static signFeePayer(txJson: string, privateKey: { s: Scalar }): string; + static signFeePayer(txJson: string, privateKey: ScalarConst): string; /** * Signs an account update. */ static signOtherAccountUpdate( txJson: string, - privateKey: { s: Scalar }, + privateKey: ScalarConst, i: number ): string; @@ -1468,7 +1468,7 @@ declare class Ledger { static publicKeyToString(publicKey: PublicKey_): string; static publicKeyOfString(publicKeyBase58: string): PublicKey_; - static privateKeyToString(privateKey: { s: Scalar }): string; + static privateKeyToString(privateKey: ScalarConst): string; static privateKeyOfString(privateKeyBase58: string): ScalarConst; static fieldToBase58(field: Field): string; static fieldOfBase58(fieldBase58: string): Field; From 9171ece3ab8d188057c69f814c12e915e340b7f4 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 1 Jun 2023 16:55:49 +0200 Subject: [PATCH 17/27] add test for consistency with ml function that's no longer used --- src/lib/ml/consistency.unit-test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/lib/ml/consistency.unit-test.ts diff --git a/src/lib/ml/consistency.unit-test.ts b/src/lib/ml/consistency.unit-test.ts new file mode 100644 index 000000000..d0b114c42 --- /dev/null +++ b/src/lib/ml/consistency.unit-test.ts @@ -0,0 +1,17 @@ +import { Ledger } from '../../snarky.js'; +import { Random, test } from '../testing/property.js'; +import { Scalar, ScalarConst } from '../scalar.js'; +import { PrivateKey } from '../signature.js'; +import { expect } from 'expect'; + +// PrivateKey.toBase58, fromBase58 + +test(Random.scalar, (s) => { + // toBase58 - check consistency with ml + let ml = Ledger.privateKeyToString(ScalarConst.fromBigint(s)); + let js = PrivateKey.fromObject({ s: Scalar.from(s) }).toBase58(); + expect(js).toEqual(ml); + + // fromBase58 - check consistency with where we started + expect(PrivateKey.fromBase58(js).s.toBigInt()).toEqual(s); +}); From c7a6c6760dbcd415ee16d8b15dbf0392f2b5a03e Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 1 Jun 2023 17:16:01 +0200 Subject: [PATCH 18/27] move private key conversion to a saner place --- src/lib/ml/consistency.unit-test.ts | 19 +++++++++++++------ src/lib/signature.ts | 14 ++++++++++++++ src/snarky.d.ts | 7 +++++-- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/lib/ml/consistency.unit-test.ts b/src/lib/ml/consistency.unit-test.ts index d0b114c42..61fdfa19f 100644 --- a/src/lib/ml/consistency.unit-test.ts +++ b/src/lib/ml/consistency.unit-test.ts @@ -1,17 +1,24 @@ -import { Ledger } from '../../snarky.js'; +import { Test } from '../../snarky.js'; import { Random, test } from '../testing/property.js'; -import { Scalar, ScalarConst } from '../scalar.js'; import { PrivateKey } from '../signature.js'; +import { Ml } from './conversion.js'; import { expect } from 'expect'; // PrivateKey.toBase58, fromBase58 -test(Random.scalar, (s) => { +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 = Ledger.privateKeyToString(ScalarConst.fromBigint(s)); - let js = PrivateKey.fromObject({ s: Scalar.from(s) }).toBase58(); + 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).s.toBigInt()).toEqual(s); + expect(PrivateKey.fromBase58(js)).toEqual(sk); + expect(Test.encoding.privateKeyOfBase58(ml)).toEqual(skMl); }); diff --git a/src/lib/signature.ts b/src/lib/signature.ts index 1b8d6ec9f..cf8d2e074 100644 --- a/src/lib/signature.ts +++ b/src/lib/signature.ts @@ -46,6 +46,20 @@ class PrivateKey extends CircuitValue { return new PrivateKey(Scalar.fromBits(bs)); } + /** + * Convert this {@link PrivateKey} to a bigint + */ + toBigInt() { + return this.s.toBigInt(); + } + + /** + * Create a {@link PrivateKey} from a bigint + */ + static fromBigInt(sk: PrivateKeyBigint) { + return new PrivateKey(Scalar.from(sk)); + } + /** * Derives the associated public key. * diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 04462ca6b..b40679f53 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -1468,8 +1468,7 @@ declare class Ledger { static publicKeyToString(publicKey: PublicKey_): string; static publicKeyOfString(publicKeyBase58: string): PublicKey_; - static privateKeyToString(privateKey: ScalarConst): string; - static privateKeyOfString(privateKeyBase58: string): ScalarConst; + static fieldToBase58(field: Field): string; static fieldOfBase58(fieldBase58: string): Field; @@ -1513,6 +1512,10 @@ declare class Ledger { } declare const Test: { + encoding: { + privateKeyToBase58(privateKey: ScalarConst): string; + privateKeyOfBase58(privateKeyBase58: string): ScalarConst; + }; transactionHash: { examplePayment(): string; serializePayment(payment: string): { data: Uint8Array }; From 60fbb5c9eb17f7527af38e5efe845b6b5e5aed06 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 1 Jun 2023 17:21:13 +0200 Subject: [PATCH 19/27] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index a884c1ffa..ec42a4a4e 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit a884c1ffadd0fc06f0ae12ea2feba554e533d4eb +Subproject commit ec42a4a4e8bae942de2347dd5db2e1576b372799 From 5baafe40f825542821b8d2ba53d9ff59eb301a57 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 1 Jun 2023 17:42:13 +0200 Subject: [PATCH 20/27] remove sign fee payer and its deps --- src/index.ts | 1 - src/lib/account_update.ts | 41 --------------------------------------- src/lib/zkapp.ts | 27 -------------------------- src/snarky.d.ts | 14 ------------- 4 files changed, 83 deletions(-) diff --git a/src/index.ts b/src/index.ts index 1a977b341..2eeaf1415 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,7 +25,6 @@ export { SmartContract, method, DeployArgs, - signFeePayer, declareMethods, Account, VerificationKey, diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index 52c8d22ec..2272b7983 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -41,7 +41,6 @@ export { ZkappCommand, addMissingSignatures, addMissingProofs, - signJsonTransaction, ZkappStateLength, Events, Actions, @@ -2049,43 +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), - Ml.fromPrivateKey(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), - Ml.fromPrivateKey(privateKey), - i - ) - ); - } - } - return JSON.stringify(zkappCommand); -} diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 44eafb494..75368a66d 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -17,7 +17,6 @@ import { Permissions, Actions, SetOrKeep, - signJsonTransaction, smartContractContext, TokenId, ZkappCommand, @@ -72,7 +71,6 @@ export { SmartContract, method, DeployArgs, - signFeePayer, declareMethods, Callback, Account, @@ -1527,31 +1525,6 @@ function Account(address: PublicKey, tokenId?: Field) { } } -function signFeePayer( - transactionJson: string, - feePayerKey: PrivateKey | string, - { - transactionFee = 0 as number | string, - feePayerNonce = undefined as number | string | undefined, - memo: feePayerMemo = undefined as string | undefined, - } -) { - let zkappCommand: Types.Json.ZkappCommand = JSON.parse(transactionJson); - if (typeof feePayerKey === 'string') - feePayerKey = PrivateKey.fromBase58(feePayerKey); - let senderAddress = feePayerKey.toPublicKey(); - if (feePayerNonce === undefined) { - let senderAccount = Mina.getAccount(senderAddress, TokenId.default); - feePayerNonce = senderAccount.nonce.toString(); - } - if (feePayerMemo) zkappCommand.memo = Ledger.memoToBase58(feePayerMemo); - zkappCommand.feePayer.body.nonce = `${feePayerNonce}`; - zkappCommand.feePayer.body.publicKey = - Ledger.publicKeyToString(senderAddress); - zkappCommand.feePayer.body.fee = `${transactionFee}`; - return signJsonTransaction(JSON.stringify(zkappCommand), feePayerKey); -} - // alternative API which can replace decorators, works in pure JS /** diff --git a/src/snarky.d.ts b/src/snarky.d.ts index b40679f53..03b261c20 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -1448,20 +1448,6 @@ declare class Ledger { */ static dummySignature(): string; - /** - * Signs a transaction as the fee payer. - */ - static signFeePayer(txJson: string, privateKey: ScalarConst): string; - - /** - * Signs an account update. - */ - static signOtherAccountUpdate( - txJson: string, - privateKey: ScalarConst, - i: number - ): string; - static customTokenId(publicKey: PublicKey_, tokenId: Field): Field; static customTokenIdChecked(publicKey: PublicKey_, tokenId: Field): Field; static createTokenAccount(publicKey: PublicKey_, tokenId: Field): string; From 18962c710db99e474eee882b5fe7a0428d83a8ff Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 1 Jun 2023 17:42:23 +0200 Subject: [PATCH 21/27] doccomments --- src/lib/scalar.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/lib/scalar.ts b/src/lib/scalar.ts index f540903f7..16ce90300 100644 --- a/src/lib/scalar.ts +++ b/src/lib/scalar.ts @@ -47,6 +47,13 @@ class Scalar { return new Scalar(bits, scalar); } + /** + * Convert this {@link Scalar} into a constant if it isn't already. + * + * If the scalar is a variable, this only works inside `asProver` or `witness` blocks. + * + * See {@link FieldVar} for an explanation of constants vs. variables. + */ toConstant(): ConstantScalar { if (this.constantValue !== undefined) return this as ConstantScalar; let [, ...bits] = this.value; @@ -61,6 +68,9 @@ class Scalar { return Scalar.from(x); } + /** + * Convert this {@link Scalar} into a bigint + */ toBigInt() { return this.#assertConstant('toBigInt'); } @@ -161,11 +171,11 @@ That means it can't be called in a @method or similar environment, and there's n return Scalar.from(z); } + // TODO don't leak 'shifting' to the user and remove these methods shift() { let x = this.#assertConstant('shift'); return Scalar.from(shift(x)); } - unshift() { let x = this.#assertConstant('unshift'); return Scalar.from(unshift(x)); From 6f6a693feb73e802983d191d6c9ec68b7c97cd0a Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 1 Jun 2023 17:42:27 +0200 Subject: [PATCH 22/27] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f94b75b33..21f294dde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,11 +22,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 From 74c8f7db7e9767fbea369f236e3c8170aa7a41d2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 1 Jun 2023 17:47:02 +0200 Subject: [PATCH 23/27] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index ec42a4a4e..5d2698783 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit ec42a4a4e8bae942de2347dd5db2e1576b372799 +Subproject commit 5d26987837be194157b470ad6c5f7b8e2fe1d5fa From b5c576772ef089ce2c0ed3ce1d0b77d897016e41 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 1 Jun 2023 17:50:36 +0200 Subject: [PATCH 24/27] remove comment --- src/lib/core.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/lib/core.ts b/src/lib/core.ts index 6ebbb664f..518e903ea 100644 --- a/src/lib/core.ts +++ b/src/lib/core.ts @@ -41,10 +41,6 @@ export { Field, Bool, Scalar, Group }; const Field = toFunctionConstructor(InternalField); type Field = InternalField; -// TODO -// const Scalar = toFunctionConstructor(InternalScalar); -// type Scalar = InternalScalar; - function toFunctionConstructor any>( Class: Class ): Class & ((...args: InferArgs) => InferReturn) { From 981bcd0f826243b7593b0a1db80bac389a223c73 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 5 Jun 2023 15:05:21 +0200 Subject: [PATCH 25/27] adapt group / scalar interaction --- src/bindings | 2 +- src/lib/group.ts | 22 +++++++++------------- src/lib/scalar.ts | 8 ++++++++ 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/bindings b/src/bindings index 3f492c7f6..ef50f88b7 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 3f492c7f6f79927f388b9fb2eb36a81b5eb8dc26 +Subproject commit ef50f88b74548391e619976cdc566fb87f1e7e61 diff --git a/src/lib/group.ts b/src/lib/group.ts index 5ad2817b5..5981348b4 100644 --- a/src/lib/group.ts +++ b/src/lib/group.ts @@ -1,5 +1,6 @@ -import { Scalar, withMessage } from './core.js'; +import { withMessage } 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'; @@ -130,20 +131,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.#toAffine(), BigInt(scalar.toJSON())); + let scalar = Scalar.from(s); + + if (this.#isConstant() && scalar.isConstant()) { + let g_proj = Pallas.scale(this.#toAffine(), 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 }); } } diff --git a/src/lib/scalar.ts b/src/lib/scalar.ts index 16ce90300..b096047d6 100644 --- a/src/lib/scalar.ts +++ b/src/lib/scalar.ts @@ -47,6 +47,14 @@ class Scalar { return new Scalar(bits, scalar); } + /** + * Check whether this {@link Scalar} is a hard-coded constant in the constraint system. + * If a {@link Scalar} is constructed outside provable code, it is a constant. + */ + isConstant(): this is Scalar & { constantValue: ScalarConst } { + return this.constantValue !== undefined; + } + /** * Convert this {@link Scalar} into a constant if it isn't already. * From aa4f06e6c73d3590aa7ca098339f3347aaaef5dc Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 8 Jun 2023 13:29:11 +0200 Subject: [PATCH 26/27] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index 262ceeaa9..dcf4eed8f 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 262ceeaa9c0d22b28acc6e9881404928725691e6 +Subproject commit dcf4eed8f3b1bf93bfb602547bddd3fc6f53cbcf From a5bcb228a3905ce8a29e6028f066876febf534cc Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 8 Jun 2023 15:45:41 +0200 Subject: [PATCH 27/27] warning for private key from bigint --- src/lib/signature.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/signature.ts b/src/lib/signature.ts index cbb27bc26..262ba95dd 100644 --- a/src/lib/signature.ts +++ b/src/lib/signature.ts @@ -55,6 +55,9 @@ class PrivateKey extends CircuitValue { /** * Create a {@link PrivateKey} from a bigint + * + * **Warning**: Private keys should be sampled from secure randomness with sufficient entropy. + * Be careful that you don't use this method to create private keys that were sampled insecurely. */ static fromBigInt(sk: PrivateKeyBigint) { return new PrivateKey(Scalar.from(sk));