UNPKG

o1js

Version:

TypeScript framework for zk-SNARKs and zkApps

343 lines (302 loc) 9.69 kB
import { Fq } from '../../bindings/crypto/finite-field.js'; import { Scalar as SignableFq } from '../../mina-signer/src/curve-bigint.js'; import { Field, checkBitLength } from './field.js'; import { FieldVar } from './core/fieldvar.js'; import { Bool } from './bool.js'; import { ShiftedScalar, field3ToShiftedScalar, fieldToShiftedScalar, } from './gadgets/native-curve.js'; import { isConstant } from './gadgets/common.js'; import { Provable } from './provable.js'; import { assert } from '../util/assert.js'; import type { HashInput } from './types/provable-derivers.js'; import { field3FromBits } from './gadgets/foreign-field.js'; export { Scalar, ScalarConst }; type ScalarConst = [0, bigint]; /** * Represents a {@link Scalar}. */ class Scalar implements ShiftedScalar { /** * We represent a scalar s in shifted form t = s - 2^255 mod q, * split into its low bit (t & 1) and high 254 bits (t >> 1). * The reason is that we can efficiently compute the scalar multiplication `(t + 2^255) * P = s * P`. */ lowBit: Bool; high254: Field; static ORDER = Fq.modulus; private constructor(lowBit: Bool, high254: Field) { this.lowBit = lowBit; this.high254 = high254; } /** * 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(s: Scalar | bigint | number | string): Scalar { if (s instanceof Scalar) return s; let t = Fq.mod(BigInt(s) - (1n << 255n)); let lowBit = new Bool((t & 1n) === 1n); let high254 = new Field(t >> 1n); return new Scalar(lowBit, high254); } /** * Provable method to convert a {@link ShiftedScalar} to a {@link Scalar}. */ static fromShiftedScalar(s: ShiftedScalar) { return new Scalar(s.lowBit, s.high254); } /** * Provable method to convert a {@link Field} into a {@link Scalar}. * * This is always possible and unambiguous, since the scalar field is larger than the base field. */ static fromField(s: Field): Scalar { let { lowBit, high254 } = fieldToShiftedScalar(s); return new Scalar(lowBit, high254); } /** * 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() { let { lowBit, high254 } = this; return isConstant(lowBit, high254); } /** * 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(): Scalar { if (this.isConstant()) return this; return Provable.toConstant(Scalar, this); } /** * Convert this {@link Scalar} into a bigint */ toBigInt() { let { lowBit, high254 } = this.toConstant(); let t = lowBit.toField().toBigInt() + 2n * high254.toBigInt(); return Fq.mod(t + (1n << 255n)); } /** * Creates a Scalar from an array of {@link Bool}. * This method is provable. */ static fromBits(bits: Bool[]): Scalar { let length = bits.length; checkBitLength('Scalar.fromBits()', length, 255); // convert bits to a 3-limb bigint let sBig = field3FromBits(bits); // convert to shifted representation let { lowBit, high254 } = field3ToShiftedScalar(sBig); return new Scalar(lowBit, high254); } /** * Returns a random {@link Scalar}. * Randomness can not be proven inside a circuit! */ static random() { return Scalar.from(Fq.random()); } // operations on constant scalars /** * Negate a scalar field element. * * **Warning**: This method is not available for provable code. */ neg() { let x = assertConstant(this, 'neg'); let z = Fq.negate(x); return Scalar.from(z); } /** * Add scalar field elements. * * **Warning**: This method is not available for provable code. */ add(y: Scalar) { let x = assertConstant(this, 'add'); let y0 = assertConstant(y, 'add'); let z = Fq.add(x, y0); return Scalar.from(z); } /** * Subtract scalar field elements. * * **Warning**: This method is not available for provable code. */ sub(y: Scalar) { let x = assertConstant(this, 'sub'); let y0 = assertConstant(y, 'sub'); let z = Fq.sub(x, y0); return Scalar.from(z); } /** * Multiply scalar field elements. * * **Warning**: This method is not available for provable code. */ mul(y: Scalar) { let x = assertConstant(this, 'mul'); let y0 = assertConstant(y, 'mul'); let z = Fq.mul(x, y0); return Scalar.from(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 = assertConstant(this, 'div'); let y0 = assertConstant(y, 'div'); let z = Fq.div(x, y0); if (z === undefined) throw Error('Scalar.div(): Division by zero'); return Scalar.from(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 = assertConstant(this, 'toFieldsCompressed'); let lowBitSize = BigInt(Fq.sizeInBits - 1); let lowBitMask = (1n << lowBitSize) - 1n; return { field: new Field(s & lowBitMask), highBit: new Bool(s >> lowBitSize === 1n), }; } // internal stuff // Provable<Scalar> /** * 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.lowBit.toField(), x.high254]; } /** * 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. * The fields are not constrained to be boolean. * * Check out {@link Scalar.toFieldsCompressed} for a user-friendly serialization * that can be used outside proofs. */ toFields(): Field[] { return Scalar.toFields(this); } /** * **Warning**: This function is mainly for internal use. Normally it is not intended to be used by a zkApp developer. * * This function is the implementation of `ProvableExtended.toInput()` for the {@link Scalar} type. * * @param value - The {@link Scalar} element to get the `input` array. * * @return An object where the `fields` key is a {@link Field} array of length 1 created from this {@link Field}. * */ static toInput(x: Scalar): HashInput { return { fields: [x.high254], packed: [[x.lowBit.toField(), 1]] }; } /** * 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 { assert( fields.length === 2, `Scalar.fromFields(): expected 2 fields, got ${fields.length}` ); let lowBit = Bool.Unsafe.fromField(fields[0]); let high254 = fields[1]; return new Scalar(lowBit, high254); } /** * Part of the {@link Provable} interface. * * Returns the size of this type in {@link Field} elements. */ static sizeInFields(): number { return 2; } /** * Part of the {@link Provable} interface. */ static check(s: Scalar) { /** * It is not necessary to constrain the range of high254, because the only provable operation on Scalar * which relies on that range is scalar multiplication -- which constrains the range itself. */ return Bool.check(s.lowBit); } static toValue(x: Scalar) { return x.toBigInt(); } static fromValue(x: bigint) { return Scalar.from(x); } // ProvableExtended<Scalar> /** * 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) { let s = assertConstant(x, 'toJSON'); return s.toString(); } /** * Serializes this Scalar to a string */ toJSON() { return Scalar.toJSON(this); } /** * 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(SignableFq.fromJSON(x)); } static empty() { return Scalar.from(0n); } } // internal helpers function assertConstant(x: Scalar, name: string): bigint { assert( x.isConstant(), `${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 x.toBigInt(); }