diff --git a/CHANGELOG.md b/CHANGELOG.md index 05e786bac..dc43cf587 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added +- Export various internal functions and types. https://github.com/o1-labs/o1js/pull/2083 +- Export part of the core cryptography layer via the `Core` namespace. https://github.com/o1-labs/o1js/pull/2083 - _Experimental_ New bindings layer for new API types. https://github.com/o1-labs/o1js/pull/2032 - _Experimental_ New API types for https://github.com/o1-labs/o1js/pull/2042 - `AccountUpdate`, `Account`, `Authorization`, `Permissions` etc. diff --git a/src/index.ts b/src/index.ts index 290b48ed2..ef8adb7b3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,7 +8,8 @@ export { AlmostForeignField, CanonicalForeignField, } from './lib/provable/foreign-field.js'; -export { createForeignCurve, ForeignCurve } from './lib/provable/crypto/foreign-curve.js'; +export { createForeignCurve, ForeignCurve, toPoint } from './lib/provable/crypto/foreign-curve.js'; +export type { FlexiblePoint } from './lib/provable/crypto/foreign-curve.js'; export { createEcdsa, EcdsaSignature } from './lib/provable/crypto/foreign-ecdsa.js'; export { ScalarField } from './lib/provable/scalar-field.js'; export { Poseidon, TokenSymbol, ProvableHashable } from './lib/provable/crypto/poseidon.js'; @@ -24,6 +25,10 @@ export type { FlexibleProvablePure, InferProvable, } from './lib/provable/types/struct.js'; + +export { provableFromClass } from './lib/provable/types/provable-derivers.js'; +export type { ProvablePureExtended } from './lib/provable/types/struct.js'; + export { From, InferValue, InferJson, IsPure } from './bindings/lib/provable-generic.js'; export { ProvableType } from './lib/provable/types/provable-intf.js'; export { provable, provablePure } from './lib/provable/types/provable-derivers.js'; @@ -187,3 +192,6 @@ namespace Experimental { } Error.stackTraceLimit = 100000; + +// export parts of the low-level bindings interface for advanced users +export * as Core from './bindings/index.js'; diff --git a/src/lib/provable/field.ts b/src/lib/provable/field.ts index fd3972a56..7f2a639b7 100644 --- a/src/lib/provable/field.ts +++ b/src/lib/provable/field.ts @@ -1055,6 +1055,8 @@ class Field { * Create an array of digits equal to the [little-endian](https://en.wikipedia.org/wiki/Endianness) byte order of the given {@link Field} element. * Note that the array has always 32 elements as the {@link Field} is a `finite-field` in the order of {@link Field.ORDER}. * + * **Warning**: This operation does _not_ affect the circuit and can't be used to prove anything about the byte representation of the {@link Field}. + * * @param value - The {@link Field} element to generate the array of bytes of. * * @return An array of digits equal to the [little-endian](https://en.wikipedia.org/wiki/Endianness) byte order of the given {@link Field} element. diff --git a/src/lib/provable/foreign-field.ts b/src/lib/provable/foreign-field.ts index 281d19fa8..d6f82f6f4 100644 --- a/src/lib/provable/foreign-field.ts +++ b/src/lib/provable/foreign-field.ts @@ -1,5 +1,5 @@ import { mod, Fp, FiniteField, createField } from '../../bindings/crypto/finite-field.js'; -import { Field, checkBitLength, withMessage } from './field.js'; +import { checkBitLength, Field, withMessage } from './field.js'; import { Provable } from './provable.js'; import { Bool } from './bool.js'; import { Tuple, TupleMap, TupleN } from '../util/types.js'; diff --git a/src/lib/provable/gadgets/basic.ts b/src/lib/provable/gadgets/basic.ts index 8ab9d4d16..c36b49da7 100644 --- a/src/lib/provable/gadgets/basic.ts +++ b/src/lib/provable/gadgets/basic.ts @@ -10,8 +10,10 @@ import { TupleN } from '../../util/types.js'; import { exists, existsOne } from '../core/exists.js'; import { createField } from '../core/field-constructor.js'; import { assert } from '../../util/assert.js'; +import { ProvableType } from '../types/provable-intf.js'; +import { Provable } from '../provable.js'; -export { assertMul, assertBilinear, arrayGet, assertOneOf, assertNotVectorEquals }; +export { assertMul, assertBilinear, arrayGet, assertOneOf, assertNotVectorEquals, arrayGetGeneric }; // internal export { reduceToScaledVar, toLinearCombination, emptyCell, linear, bilinear, ScaledVar, Constant }; @@ -363,3 +365,25 @@ function getLinear(x: ScaledVar | Constant): [[bigint, VarFieldVar], bigint] { } const ScaledVar = { isVar, getVar, isConst, getConst }; + +/** + * Get value from array in O(n) constraints. + * + * Assumes that index is in [0, n), returns an unconstrained result otherwise. + */ +function arrayGetGeneric(type: ProvableType, array: T[], index: Field) { + type = ProvableType.get(type); + // witness result + let a = Provable.witness(type, () => array[Number(index)]); + let aFields = type.toFields(a); + + // constrain each field of the result + let size = type.sizeInFields(); + let arrays = array.map(type.toFields); + + for (let j = 0; j < size; j++) { + let arrayFieldsJ = arrays.map((x) => x[j]); + arrayGet(arrayFieldsJ, index).assertEquals(aFields[j]); + } + return a; +} diff --git a/src/lib/provable/gadgets/elliptic-curve.ts b/src/lib/provable/gadgets/elliptic-curve.ts index d46319612..72a69a6ef 100644 --- a/src/lib/provable/gadgets/elliptic-curve.ts +++ b/src/lib/provable/gadgets/elliptic-curve.ts @@ -15,10 +15,9 @@ import { import { Bool } from '../bool.js'; import { provable } from '../types/provable-derivers.js'; import { assertPositiveInteger } from '../../../bindings/crypto/non-negative.js'; -import { arrayGet, assertNotVectorEquals } from './basic.js'; +import { arrayGetGeneric, assertNotVectorEquals } from './basic.js'; import { sliceField3 } from './bit-slices.js'; import { exists } from '../core/exists.js'; -import { ProvableType } from '../types/provable-intf.js'; // external API export { EllipticCurve, Point, Ecdsa }; @@ -673,28 +672,6 @@ function simpleMapToCurve(x: bigint, Curve: CurveAffine) { return p; } -/** - * Get value from array in O(n) constraints. - * - * Assumes that index is in [0, n), returns an unconstrained result otherwise. - */ -function arrayGetGeneric(type: ProvableType, array: T[], index: Field) { - type = ProvableType.get(type); - // witness result - let a = Provable.witness(type, () => array[Number(index)]); - let aFields = type.toFields(a); - - // constrain each field of the result - let size = type.sizeInFields(); - let arrays = array.map(type.toFields); - - for (let j = 0; j < size; j++) { - let arrayFieldsJ = arrays.map((x) => x[j]); - arrayGet(arrayFieldsJ, index).assertEquals(aFields[j]); - } - return a; -} - // type/conversion helpers const Point = { diff --git a/src/lib/provable/gadgets/foreign-field.ts b/src/lib/provable/gadgets/foreign-field.ts index 2e1a1266e..61d074a0f 100644 --- a/src/lib/provable/gadgets/foreign-field.ts +++ b/src/lib/provable/gadgets/foreign-field.ts @@ -61,6 +61,7 @@ const ForeignField = { assertLessThan, assertLessThanOrEqual, + assertEquals, equals, toCanonical, @@ -406,6 +407,19 @@ function equals(x: Field3, c: bigint, f: bigint) { } } +// Field3 equality checking that each of the 3 limbs are exactly the same (not modular equality). +function assertEquals(x: Field3, y: Field3) { + // constant case + if (Field3.isConstant(x) && Field3.isConstant(y)) { + assert(Field3.toBigint(x) === Field3.toBigint(y), 'assertEqual: got x != y'); + return; + } + //provable case + x[0].assertEquals(y[0]); + x[1].assertEquals(y[1]); + x[2].assertEquals(y[2]); +} + /** * Convert x, which may be unreduced, to a canonical representative < f. * @@ -471,6 +485,10 @@ const Field3 = { return x; }, } satisfies ProvablePureExtended, + /** + * Splits a bigint into three limbs using bitwise operations. + */ + split, }; type Field2 = [Field, Field]; diff --git a/src/lib/provable/gadgets/gadgets.ts b/src/lib/provable/gadgets/gadgets.ts index ffb9d13fb..3fa665086 100644 --- a/src/lib/provable/gadgets/gadgets.ts +++ b/src/lib/provable/gadgets/gadgets.ts @@ -10,6 +10,11 @@ import { rangeCheck64, rangeCheckN, isDefinitelyInRangeN, + l2Mask, + lMask, + l2, + l, + l3, } from './range-check.js'; import { not, @@ -29,7 +34,8 @@ import { SHA2 } from './sha2.js'; import { SHA256 } from './sha256.js'; import { BLAKE2B } from './blake2b.js'; import { rangeCheck3x12 } from './lookup.js'; -import { arrayGet } from './basic.js'; +import { arrayGet, arrayGetGeneric } from './basic.js'; +import { sliceField3 } from './bit-slices.js'; export { Gadgets, Field3, ForeignFieldSum }; @@ -51,6 +57,12 @@ const Gadgets = { * **Note**: This saves n constraints compared to `Provable.switch(array.map((_, i) => index.equals(i)), type, array)`. */ arrayGet, + /** + * Get value from array in O(n) constraints. + * + * Assumes that index is in [0, n), returns an unconstrained result otherwise. + */ + arrayGetGeneric, /** * Asserts that the input value is in the range [0, 2^64). @@ -626,6 +638,17 @@ const Gadgets = { return ForeignField.add(x, y, f); }, + /** + * Check whether `x = c mod f` + * + * `c` is a constant, and we require `c` in `[0, f)` + * + * Assumes that `x` is almost reduced modulo `f`, so we know that `x` might be `c` or `c + f`, but not `c + 2f`, `c + 3f`, ... + */ + equals(x: Field3, c: bigint, f: bigint) { + return ForeignField.equals(x, c, f); + }, + /** * Foreign field subtraction: `x - y mod f` * @@ -788,9 +811,10 @@ const Gadgets = { x: Field3 | ForeignFieldSum, y: Field3 | ForeignFieldSum, z: Field3 | ForeignFieldSum, - f: bigint + f: bigint, + message?: string ) { - return ForeignField.assertMul(x, y, z, f); + return ForeignField.assertMul(x, y, z, f, message); }, /** @@ -870,6 +894,12 @@ const Gadgets = { ForeignField.assertLessThanOrEqual(x, f); }, + /** + * Proves that x is equal to y. + */ + assertEquals(x: Field3, y: Field3) { + ForeignField.assertEquals(x, y); + }, /** * Convert x, which may be unreduced, to a canonical representative xR < f * such that x = xR mod f @@ -880,6 +910,12 @@ const Gadgets = { toCanonical(x: Field3, f: bigint) { return ForeignField.toCanonical(x, f); }, + /** + * Provable method for slicing a 3x88-bit bigint into smaller bit chunks of length `chunkSize` + * + * This serves as a range check that the input is in [0, 2^maxBits) + */ + sliceField3, }, /** @@ -888,6 +924,7 @@ const Gadgets = { * **Note:** This interface does not contain any provable methods. */ Field3, + /** * Division modulo 2^32. The operation decomposes a {@link Field} element in the range [0, 2^64) into two 32-bit limbs, `remainder` and `quotient`, using the following equation: `n = quotient * 2^32 + remainder`. * @@ -1023,4 +1060,15 @@ const Gadgets = { * */ BLAKE2B: BLAKE2B, + + /** + * Default limb size constants mostly used in range checks. + */ + Constants: { + l2Mask, + l, + l2, + l3, + lMask, + }, }; diff --git a/src/snarky.d.ts b/src/snarky.d.ts index c1fd6a413..05f928ebd 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -97,6 +97,7 @@ declare const Snarky: { * Check whether we are inside an asProver or exists block */ inProverBlock(): boolean; + /** * Setting that controls whether snarky throws an exception on violated constraint. */