UNPKG

o1js

Version:

TypeScript framework for zk-SNARKs and zkApps

295 lines 9.95 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 { Bool } from './bool.js'; import { field3ToShiftedScalar, fieldToShiftedScalar, shiftedScalarToField3, } from './gadgets/native-curve.js'; import { isConstant } from './gadgets/common.js'; import { Provable } from './provable.js'; import { assert } from '../util/assert.js'; import { field3FromBits } from './gadgets/foreign-field.js'; export { Scalar }; /** * Represents a {@link Scalar}. */ class Scalar { constructor(lowBit, high254) { 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) { 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); } /** * @internal * Provable method to convert a {@link ShiftedScalar} to a {@link Scalar}. */ static fromShiftedScalar(s) { 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) { 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); } /** * @internal * 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() { 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) { 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) { 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) { 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) { 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) { 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() { 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) { 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() { 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(value) { return { fields: [value.high254], packed: [[value.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) { 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() { return 2; } /** * Part of the {@link Provable} interface. */ static check(s) { /** * 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 toCanonical(s) { // we convert to a field3, which always works // and then back, which proves the result is canonical let sBig = shiftedScalarToField3(s); let sCanonical = field3ToShiftedScalar(sBig); return Scalar.fromShiftedScalar(sCanonical); } static toValue(x) { return x.toBigInt(); } static fromValue(x) { 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) { 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) { return Scalar.from(SignableFq.fromJSON(x)); } static empty() { return Scalar.from(0n); } } Scalar.ORDER = Fq.modulus; // internal helpers function assertConstant(x, name) { 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(); } //# sourceMappingURL=scalar.js.map