diff --git a/CHANGELOG.md b/CHANGELOG.md index 60092f496c..7cc1cf260b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/ed198f305...HEAD) +### Added + +- `ForeignField`-based representation of scalars via `ScalarField` https://github.com/o1-labs/o1js/pull/1705 + ## [1.4.0](https://github.com/o1-labs/o1js/compare/40c597775...ed198f305) - 2024-06-25 ### Added diff --git a/src/index.ts b/src/index.ts index 3778236825..a128da7318 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,6 +19,7 @@ export { EcdsaSignature, EcdsaSignatureV2, } from './lib/provable/crypto/foreign-ecdsa.js'; +export { ScalarField } from './lib/provable/scalar-field.js'; export { Poseidon, TokenSymbol, diff --git a/src/lib/provable/scalar-field.ts b/src/lib/provable/scalar-field.ts new file mode 100644 index 0000000000..91c085a025 --- /dev/null +++ b/src/lib/provable/scalar-field.ts @@ -0,0 +1,47 @@ +import { Fq } from '../../bindings/crypto/finite-field.js'; +import { ForeignField, createForeignField } from './foreign-field.js'; +import { field3ToShiftedScalar } from './gadgets/native-curve.js'; +import { Provable } from './provable.js'; +import { Scalar } from './scalar.js'; + +export { ScalarField }; + +/** + * ForeignField representing the scalar field of Pallas and the base field of Vesta + */ +class ScalarField extends createForeignField(Fq.modulus) { + /** + * Provable method to convert a {@link ScalarField} into a {@link Scalar} + */ + public toScalar(): Scalar { + return ScalarField.toScalar(this); + } + + public static toScalar(field: ForeignField) { + if (field.modulus !== Fq.modulus) { + throw new Error( + 'Only ForeignFields with Fq modulus are convertable into a scalar' + ); + } + const field3 = field.value; + const shiftedScalar = field3ToShiftedScalar(field3); + return Scalar.fromShiftedScalar(shiftedScalar); + } + + /** + * Converts this {@link Scalar} into a {@link ScalarField} + */ + static fromScalar(s: Scalar): ScalarField { + if (s.lowBit.isConstant() && s.high254.isConstant()) { + return new ScalarField(s.toBigInt()); + } + const field = Provable.witness(ScalarField.provable, () => { + return s.toBigInt(); + }); + const foreignField = new ScalarField(field); + const scalar = foreignField.toScalar(); + Provable.assertEqual(Scalar, s, scalar); + + return foreignField; + } +} diff --git a/src/lib/provable/scalar.ts b/src/lib/provable/scalar.ts index fe4602fbd1..dc96672f94 100644 --- a/src/lib/provable/scalar.ts +++ b/src/lib/provable/scalar.ts @@ -50,6 +50,13 @@ class Scalar implements ShiftedScalar { 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}. * diff --git a/src/lib/provable/test/scalar.test.ts b/src/lib/provable/test/scalar.test.ts index bf0f717d21..1d06866d6d 100644 --- a/src/lib/provable/test/scalar.test.ts +++ b/src/lib/provable/test/scalar.test.ts @@ -1,4 +1,4 @@ -import { Field, Provable, Scalar } from 'o1js'; +import { Field, Provable, Scalar, ScalarField } from 'o1js'; describe('scalar', () => { describe('scalar', () => { @@ -33,6 +33,18 @@ describe('scalar', () => { }); }); + describe('toScalarField / fromScalarField', () => { + it('should return the same', async () => { + const s = Scalar.random(); + await Provable.runAndCheck(() => { + const scalar = Provable.witness(Scalar, () => s); + const scalarField = ScalarField.fromScalar(scalar); + const scalar2 = scalarField.toScalar(); + Provable.assertEqual(scalar, scalar2); + }); + }); + }); + describe('random', () => { it('two different calls should be different', async () => { await Provable.runAndCheck(() => {