From 45a85510d9f58176cec49ed3776a145317dd7c92 Mon Sep 17 00:00:00 2001 From: gabrielbosio Date: Tue, 12 Mar 2024 12:02:18 -0300 Subject: [PATCH 01/17] Adapt ForeignCurve and Unconstrained to bn254 --- src/lib/circuit-value-bn254.ts | 123 ++++++++++++++++++++++++- src/lib/foreign-curve-bn254.ts | 4 +- src/lib/gadgets/foreign-field-bn254.ts | 2 +- 3 files changed, 125 insertions(+), 4 deletions(-) diff --git a/src/lib/circuit-value-bn254.ts b/src/lib/circuit-value-bn254.ts index 417e521cf6..8a8e0eaa2d 100644 --- a/src/lib/circuit-value-bn254.ts +++ b/src/lib/circuit-value-bn254.ts @@ -1,5 +1,5 @@ import 'reflect-metadata'; -import { ProvablePureBn254 } from '../snarky.js'; +import { ProvablePureBn254, Snarky } from '../snarky.js'; import { provablePure, provableTuple, @@ -13,6 +13,8 @@ import type { } from '../bindings/lib/provable-snarky.js'; import { FieldBn254 } from './field-bn254.js'; import { ProvableBn254 } from './provable-bn254.js'; +import { assert } from './errors.js'; +import { inCheckedComputation } from './provable-context-bn254.js'; // external API export { @@ -20,6 +22,7 @@ export { ProvablePureExtendedBn254, provable, provablePure, + Unconstrained, }; // internal API @@ -43,3 +46,121 @@ type ProvableExtendedBn254 = ProvableBn254 & type ProvablePureExtendedBn254 = ProvablePureBn254 & ProvableExtensionBn254; + +/** +* Container which holds an unconstrained value. This can be used to pass values +* between the out-of-circuit blocks in provable code. +* +* Invariants: +* - An `Unconstrained`'s value can only be accessed in auxiliary contexts. +* - An `Unconstrained` can be empty when compiling, but never empty when running as the prover. +* (there is no way to create an empty `Unconstrained` in the prover) +* +* @example +* ```ts +* let x = Unconstrained.from(0n); +* +* class MyContract extends SmartContract { +* `@method` myMethod(x: Unconstrained) { +* +* Provable.witness(Field, () => { +* // we can access and modify `x` here +* let newValue = x.get() + otherField.toBigInt(); +* x.set(newValue); +* +* // ... +* }); +* +* // throws an error! +* x.get(); +* } +* ``` +*/ +class Unconstrained { + private option: + | { isSome: true; value: T } + | { isSome: false; value: undefined }; + + private constructor(isSome: boolean, value?: T) { + this.option = { isSome, value: value as any }; + } + + /** + * Read an unconstrained value. + * + * Note: Can only be called outside provable code. + */ + get(): T { + if (inCheckedComputation() && !Snarky.bn254.run.inProverBlock()) + throw Error(`You cannot use Unconstrained.get() in provable code. + +The only place where you can read unconstrained values is in Provable.witness() +and Provable.asProver() blocks, which execute outside the proof. +`); + assert(this.option.isSome, 'Empty `Unconstrained`'); // never triggered + return this.option.value; + } + + /** + * Modify the unconstrained value. + */ + set(value: T) { + this.option = { isSome: true, value }; + } + + /** + * Set the unconstrained value to the same as another `Unconstrained`. + */ + setTo(value: Unconstrained) { + this.option = value.option; + } + + /** + * Create an `Unconstrained` with the given `value`. + * + * Note: If `T` contains provable types, `Unconstrained.from` is an anti-pattern, + * because it stores witnesses in a space that's intended to be used outside the proof. + * Something like the following should be used instead: + * + * ```ts + * let xWrapped = Unconstrained.witness(() => Provable.toConstant(type, x)); + * ``` + */ + static from(value: T) { + return new Unconstrained(true, value); + } + + /** + * Create an `Unconstrained` from a witness computation. + */ + static witness(compute: () => T) { + return ProvableBn254.witness( + Unconstrained.provable, + () => new Unconstrained(true, compute()) + ); + } + + /** + * Update an `Unconstrained` by a witness computation. + */ + updateAsProver(compute: (value: T) => T) { + return ProvableBn254.asProver(() => { + let value = this.get(); + this.set(compute(value)); + }); + } + + static provable: ProvableBn254> & { + toInput: (x: Unconstrained) => { + fields?: FieldBn254[]; + packed?: [FieldBn254, number][]; + }; + } = { + sizeInFields: () => 0, + toFields: () => [], + toAuxiliary: (t?: any) => [t ?? new Unconstrained(false)], + fromFields: (_, [t]) => t, + check: () => { }, + toInput: () => ({}), + }; +} diff --git a/src/lib/foreign-curve-bn254.ts b/src/lib/foreign-curve-bn254.ts index f111080307..145f984d3a 100644 --- a/src/lib/foreign-curve-bn254.ts +++ b/src/lib/foreign-curve-bn254.ts @@ -3,7 +3,6 @@ import { CurveAffine, createCurveAffine, } from '../bindings/crypto/elliptic-curve.js'; -import { ProvablePureExtended } from './circuit-value.js'; import { AlmostForeignFieldBn254, ForeignFieldBn254, createForeignFieldBn254 } from './foreign-field-bn254.js'; import { EllipticCurveBn254, PointBn254 } from './gadgets/elliptic-curve-bn254.js'; import { Field3 } from './gadgets/foreign-field-bn254.js'; @@ -11,6 +10,7 @@ import { assert } from './gadgets/common-bn254.js'; import { ProvableBn254 } from './provable-bn254.js'; import { provableFromClass } from '../bindings/lib/provable-snarky.js'; import { FieldConst, FieldVar } from './field-bn254.js'; +import { ProvablePureExtendedBn254 } from './circuit-value-bn254.js'; // external API export { createForeignCurveBn254, ForeignCurveBn254 }; @@ -293,7 +293,7 @@ class ForeignCurveBn254 { static _Bigint?: CurveAffine; static _Field?: typeof AlmostForeignFieldBn254; static _Scalar?: typeof AlmostForeignFieldBn254; - static _provable?: ProvablePureExtended< + static _provable?: ProvablePureExtendedBn254< ForeignCurveBn254, { x: string; y: string } >; diff --git a/src/lib/gadgets/foreign-field-bn254.ts b/src/lib/gadgets/foreign-field-bn254.ts index 2f5788aa56..4f6d0b9cb7 100644 --- a/src/lib/gadgets/foreign-field-bn254.ts +++ b/src/lib/gadgets/foreign-field-bn254.ts @@ -7,7 +7,7 @@ import { } from '../../bindings/crypto/finite-field.js'; import { provableTuple } from '../../bindings/lib/provable-snarky.js'; import { BoolBn254 } from '../bool-bn254.js'; -import { Unconstrained } from '../circuit-value.js'; +import { Unconstrained } from '../circuit-value-bn254.js'; import { FieldBn254 } from '../field-bn254.js'; import { GatesBn254, foreignFieldAdd } from '../gates-bn254.js'; import { modifiedField } from '../provable-types/fields.js'; From cf757f6af9e194a2b7487e32a8c62f659a3daaa6 Mon Sep 17 00:00:00 2001 From: gabrielbosio Date: Tue, 12 Mar 2024 15:39:48 -0300 Subject: [PATCH 02/17] [WIP] Start adding bn254 modules for verifier --- src/lib/circuit-value-bn254.ts | 652 ++++++++++++++++++++++-- src/lib/field-bn254.ts | 14 +- src/lib/gadgets/basic-bn254.ts | 134 +++++ src/lib/gadgets/bit-slices-bn254.ts | 156 ++++++ src/lib/gadgets/elliptic-curve-bn254.ts | 4 +- src/lib/gadgets/foreign-field-bn254.ts | 4 +- src/lib/hash-bn254.ts | 247 +++++++++ src/lib/provable-types/packed-bn254.ts | 271 ++++++++++ 8 files changed, 1431 insertions(+), 51 deletions(-) create mode 100644 src/lib/gadgets/basic-bn254.ts create mode 100644 src/lib/gadgets/bit-slices-bn254.ts create mode 100644 src/lib/hash-bn254.ts create mode 100644 src/lib/provable-types/packed-bn254.ts diff --git a/src/lib/circuit-value-bn254.ts b/src/lib/circuit-value-bn254.ts index 8a8e0eaa2d..39c62699c2 100644 --- a/src/lib/circuit-value-bn254.ts +++ b/src/lib/circuit-value-bn254.ts @@ -1,40 +1,55 @@ import 'reflect-metadata'; import { ProvablePureBn254, Snarky } from '../snarky.js'; +import { FieldBn254, BoolBn254 } from './core-bn254.js'; import { + provable, provablePure, provableTuple, HashInput, -} from '../bindings/lib/provable-snarky.js'; -import { provable } from '../bindings/lib/provable-snarky-bn254.js'; + NonMethods, +} from '../bindings/lib/provable-snarky-bn254.js'; import type { InferJson, InferProvable, InferredProvable, -} from '../bindings/lib/provable-snarky.js'; -import { FieldBn254 } from './field-bn254.js'; + IsPure, +} from '../bindings/lib/provable-snarky-bn254.js'; import { ProvableBn254 } from './provable-bn254.js'; import { assert } from './errors.js'; import { inCheckedComputation } from './provable-context-bn254.js'; +import { Proof } from './proof-system.js'; // external API export { + CircuitValue, ProvableExtendedBn254, ProvablePureExtendedBn254, + prop, + arrayProp, + matrixProp, provable, provablePure, + Struct, + FlexibleProvableBn254, + FlexibleProvablePureBn254, Unconstrained, }; // internal API export { provableTuple, + AnyConstructor, + cloneCircuitValue, + circuitValueEquals, + toConstant, InferProvable, HashInput, InferJson, InferredProvable, + StructNoJson, }; -type ProvableExtensionBn254 = { +type ProvableExtension = { toInput: (x: T) => { fields?: FieldBn254[]; packed?: [FieldBn254, number][] }; toJSON: (x: T) => TJson; fromJSON: (x: TJson) => T; @@ -42,40 +57,474 @@ type ProvableExtensionBn254 = { }; type ProvableExtendedBn254 = ProvableBn254 & - ProvableExtensionBn254; - + ProvableExtension; type ProvablePureExtendedBn254 = ProvablePureBn254 & - ProvableExtensionBn254; + ProvableExtension; + +type Struct = ProvableExtendedBn254> & + Constructor & { _isStruct: true }; +type StructPure = ProvablePureBn254> & + ProvableExtension> & + Constructor & { _isStruct: true }; +type FlexibleProvableBn254 = ProvableBn254 | Struct; +type FlexibleProvablePureBn254 = ProvablePureBn254 | StructPure; + +type Constructor = new (...args: any) => T; +type AnyConstructor = Constructor; /** -* Container which holds an unconstrained value. This can be used to pass values -* between the out-of-circuit blocks in provable code. -* -* Invariants: -* - An `Unconstrained`'s value can only be accessed in auxiliary contexts. -* - An `Unconstrained` can be empty when compiling, but never empty when running as the prover. -* (there is no way to create an empty `Unconstrained` in the prover) -* -* @example -* ```ts -* let x = Unconstrained.from(0n); -* -* class MyContract extends SmartContract { -* `@method` myMethod(x: Unconstrained) { -* -* Provable.witness(Field, () => { -* // we can access and modify `x` here -* let newValue = x.get() + otherField.toBigInt(); -* x.set(newValue); -* -* // ... -* }); -* -* // throws an error! -* x.get(); -* } -* ``` -*/ + * @deprecated `CircuitValue` is deprecated in favor of {@link Struct}, which features a simpler API and better typing. + */ +abstract class CircuitValue { + constructor(...props: any[]) { + // if this is called with no arguments, do nothing, to support simple super() calls + if (props.length === 0) return; + + let fields = this.constructor.prototype._fields; + if (fields === undefined) return; + if (props.length !== fields.length) { + throw Error( + `${this.constructor.name} constructor called with ${props.length} arguments, but expected ${fields.length}` + ); + } + for (let i = 0; i < fields.length; ++i) { + let [key] = fields[i]; + (this as any)[key] = props[i]; + } + } + + static fromObject( + this: T, + value: NonMethods> + ): InstanceType { + return Object.assign(Object.create(this.prototype), value); + } + + static sizeInFields(): number { + const fields: [string, any][] = (this as any).prototype._fields; + return fields.reduce((acc, [_, typ]) => acc + typ.sizeInFields(), 0); + } + + static toFields( + this: T, + v: InstanceType + ): FieldBn254[] { + const res: FieldBn254[] = []; + const fields = this.prototype._fields; + if (fields === undefined || fields === null) { + return res; + } + for (let i = 0, n = fields.length; i < n; ++i) { + const [key, propType] = fields[i]; + const subElts: FieldBn254[] = propType.toFields((v as any)[key]); + subElts.forEach((x) => res.push(x)); + } + return res; + } + + static toAuxiliary(): [] { + return []; + } + + static toInput( + this: T, + v: InstanceType + ): HashInput { + let input: HashInput = { fields: [], packed: [] }; + let fields = this.prototype._fields; + if (fields === undefined) return input; + for (let i = 0, n = fields.length; i < n; ++i) { + let [key, type] = fields[i]; + if ('toInput' in type) { + input = HashInput.append(input, type.toInput(v[key])); + continue; + } + // as a fallback, use toFields on the type + // TODO: this is problematic -- ignores if there's a toInput on a nested type + // so, remove this? should every provable define toInput? + let xs: FieldBn254[] = type.toFields(v[key]); + input.fields!.push(...xs); + } + return input; + } + + toFields(): FieldBn254[] { + return (this.constructor as any).toFields(this); + } + + toJSON(): any { + return (this.constructor as any).toJSON(this); + } + + toConstant(): this { + return (this.constructor as any).toConstant(this); + } + + equals(x: this) { + return ProvableBn254.equal(this, x); + } + + assertEquals(x: this) { + ProvableBn254.assertEqual(this, x); + } + + isConstant() { + return this.toFields().every((x) => x.isConstant()); + } + + static fromFields( + this: T, + xs: FieldBn254[] + ): InstanceType { + const fields: [string, any][] = (this as any).prototype._fields; + if (xs.length < fields.length) { + throw Error( + `${this.name}.fromFields: Expected ${fields.length} field elements, got ${xs?.length}` + ); + } + let offset = 0; + const props: any = {}; + for (let i = 0; i < fields.length; ++i) { + const [key, propType] = fields[i]; + const propSize = propType.sizeInFields(); + const propVal = propType.fromFields( + xs.slice(offset, offset + propSize), + [] + ); + props[key] = propVal; + offset += propSize; + } + return Object.assign(Object.create(this.prototype), props); + } + + static check(this: T, v: InstanceType) { + const fields = (this as any).prototype._fields; + if (fields === undefined || fields === null) { + return; + } + for (let i = 0; i < fields.length; ++i) { + const [key, propType] = fields[i]; + const value = (v as any)[key]; + if (propType.check === undefined) + throw Error('bug: CircuitValue without .check()'); + propType.check(value); + } + } + + static toConstant( + this: T, + t: InstanceType + ): InstanceType { + const xs: FieldBn254[] = (this as any).toFields(t); + return (this as any).fromFields(xs.map((x) => x.toConstant())); + } + + static toJSON(this: T, v: InstanceType) { + const res: any = {}; + if ((this as any).prototype._fields !== undefined) { + const fields: [string, any][] = (this as any).prototype._fields; + fields.forEach(([key, propType]) => { + res[key] = propType.toJSON((v as any)[key]); + }); + } + return res; + } + + static fromJSON( + this: T, + value: any + ): InstanceType { + let props: any = {}; + let fields: [string, any][] = (this as any).prototype._fields; + if (typeof value !== 'object' || value === null || Array.isArray(value)) { + throw Error(`${this.name}.fromJSON(): invalid input ${value}`); + } + if (fields !== undefined) { + for (let i = 0; i < fields.length; ++i) { + let [key, propType] = fields[i]; + if (value[key] === undefined) { + throw Error(`${this.name}.fromJSON(): invalid input ${value}`); + } else { + props[key] = propType.fromJSON(value[key]); + } + } + } + return Object.assign(Object.create(this.prototype), props); + } + + static empty(): InstanceType { + const fields: [string, any][] = (this as any).prototype._fields ?? []; + let props: any = {}; + fields.forEach(([key, propType]) => { + props[key] = propType.empty(); + }); + return Object.assign(Object.create(this.prototype), props); + } +} + +function prop(this: any, target: any, key: string) { + const fieldType = Reflect.getMetadata('design:type', target, key); + if (!target.hasOwnProperty('_fields')) { + target._fields = []; + } + if (fieldType === undefined) { + } else if (fieldType.toFields && fieldType.fromFields) { + target._fields.push([key, fieldType]); + } else { + console.log( + `warning: property ${key} missing field element conversion methods` + ); + } +} + +function arrayProp(elementType: FlexibleProvableBn254, length: number) { + return function (target: any, key: string) { + if (!target.hasOwnProperty('_fields')) { + target._fields = []; + } + target._fields.push([key, ProvableBn254.Array(elementType, length)]); + }; +} + +function matrixProp( + elementType: FlexibleProvableBn254, + nRows: number, + nColumns: number +) { + return function (target: any, key: string) { + if (!target.hasOwnProperty('_fields')) { + target._fields = []; + } + target._fields.push([ + key, + ProvableBn254.Array(ProvableBn254.Array(elementType, nColumns), nRows), + ]); + }; +} + +/** + * `Struct` lets you declare composite types for use in o1js circuits. + * + * These composite types can be passed in as arguments to smart contract methods, used for on-chain state variables + * or as event / action types. + * + * Here's an example of creating a "Voter" struct, which holds a public key and a collection of votes on 3 different proposals: + * ```ts + * let Vote = { hasVoted: BoolBn254, inFavor: BoolBn254 }; + * + * class Voter extends Struct({ + * publicKey: PublicKey, + * votes: [Vote, Vote, Vote] + * }) {} + * + * // use Voter as SmartContract input: + * class VoterContract extends SmartContract { + * \@method register(voter: Voter) { + * // ... + * } + * } + * ``` + * In this example, there are no instance methods on the class. This makes `Voter` type-compatible with an anonymous object of the form + * `{ publicKey: PublicKey, votes: Vote[] }`. + * This mean you don't have to create instances by using `new Voter(...)`, you can operate with plain objects: + * ```ts + * voterContract.register({ publicKey, votes }); + * ``` + * + * On the other hand, you can also add your own methods: + * ```ts + * class Voter extends Struct({ + * publicKey: PublicKey, + * votes: [Vote, Vote, Vote] + * }) { + * vote(index: number, inFavor: BoolBn254) { + * let vote = this.votes[i]; + * vote.hasVoted = BoolBn254(true); + * vote.inFavor = inFavor; + * } + * } + * ``` + * + * In this case, you'll need the constructor to create instances of `Voter`. It always takes as input the plain object: + * ```ts + * let emptyVote = { hasVoted: BoolBn254(false), inFavor: BoolBn254(false) }; + * let voter = new Voter({ publicKey, votes: Array(3).fill(emptyVote) }); + * voter.vote(1, BoolBn254(true)); + * ``` + * + * In addition to creating types composed of FieldBn254 elements, you can also include auxiliary data which does not become part of the proof. + * This, for example, allows you to re-use the same type outside o1js methods, where you might want to store additional metadata. + * + * To declare non-proof values of type `string`, `number`, etc, you can use the built-in objects `String`, `Number`, etc. + * Here's how we could add the voter's name (a string) as auxiliary data: + * ```ts + * class Voter extends Struct({ + * publicKey: PublicKey, + * votes: [Vote, Vote, Vote], + * fullName: String + * }) {} + * ``` + * + * Again, it's important to note that this doesn't enable you to prove anything about the `fullName` string. + * From the circuit point of view, it simply doesn't exist! + * + * @param type Object specifying the layout of the `Struct` + * @param options Advanced option which allows you to force a certain order of object keys + * @returns Class which you can extend + */ +function Struct< + A, + T extends InferProvable = InferProvable, + J extends InferJson = InferJson, + Pure extends boolean = IsPure +>( + type: A +): (new (value: T) => T) & { _isStruct: true } & (Pure extends true + ? ProvablePureBn254 + : ProvableBn254) & { + toInput: (x: T) => { + fields?: FieldBn254[] | undefined; + packed?: [FieldBn254, number][] | undefined; + }; + toJSON: (x: T) => J; + fromJSON: (x: J) => T; + empty: () => T; + } { + class Struct_ { + static type = provable(type); + static _isStruct: true; + + constructor(value: T) { + Object.assign(this, value); + } + /** + * This method is for internal use, you will probably not need it. + * @returns the size of this struct in field elements + */ + static sizeInFields() { + return this.type.sizeInFields(); + } + /** + * This method is for internal use, you will probably not need it. + * @param value + * @returns the raw list of field elements that represent this struct inside the proof + */ + static toFields(value: T): FieldBn254[] { + return this.type.toFields(value); + } + /** + * This method is for internal use, you will probably not need it. + * @param value + * @returns the raw non-field element data contained in the struct + */ + static toAuxiliary(value: T): any[] { + return this.type.toAuxiliary(value); + } + /** + * This method is for internal use, you will probably not need it. + * @param value + * @returns a representation of this struct as field elements, which can be hashed efficiently + */ + static toInput(value: T): HashInput { + return this.type.toInput(value); + } + /** + * Convert this struct to a JSON object, consisting only of numbers, strings, booleans, arrays and plain objects. + * @param value + * @returns a JSON representation of this struct + */ + static toJSON(value: T): J { + return this.type.toJSON(value) as J; + } + /** + * Convert from a JSON object to an instance of this struct. + * @param json + * @returns a JSON representation of this struct + */ + static fromJSON(json: J): T { + let value = this.type.fromJSON(json); + let struct = Object.create(this.prototype); + return Object.assign(struct, value); + } + /** + * Create an instance of this struct filled with default values + * @returns an empty instance of this struct + */ + static empty(): T { + let value = this.type.empty(); + let struct = Object.create(this.prototype); + return Object.assign(struct, value); + } + /** + * This method is for internal use, you will probably not need it. + * Method to make assertions which should be always made whenever a struct of this type is created in a proof. + * @param value + */ + static check(value: T) { + return this.type.check(value); + } + /** + * This method is for internal use, you will probably not need it. + * Recover a struct from its raw field elements and auxiliary data. + * @param fields the raw fields elements + * @param aux the raw non-field element data + */ + static fromFields(fields: FieldBn254[], aux: any[]) { + let value = this.type.fromFields(fields, aux) as T; + let struct = Object.create(this.prototype); + return Object.assign(struct, value); + } + } + return Struct_ as any; +} + +function StructNoJson< + A, + T extends InferProvable = InferProvable, + Pure extends boolean = IsPure +>( + type: A +): (new (value: T) => T) & { _isStruct: true } & (Pure extends true + ? ProvablePureBn254 + : ProvableBn254) & { + toInput: (x: T) => { + fields?: FieldBn254[] | undefined; + packed?: [FieldBn254, number][] | undefined; + }; + empty: () => T; + } { + return Struct(type) satisfies ProvableBn254 as any; +} + +/** + * Container which holds an unconstrained value. This can be used to pass values + * between the out-of-circuit blocks in provable code. + * + * Invariants: + * - An `Unconstrained`'s value can only be accessed in auxiliary contexts. + * - An `Unconstrained` can be empty when compiling, but never empty when running as the prover. + * (there is no way to create an empty `Unconstrained` in the prover) + * + * @example + * ```ts + * let x = Unconstrained.from(0n); + * + * class MyContract extends SmartContract { + * `@method` myMethod(x: Unconstrained) { + * + * ProvableBn254.witness(FieldBn254, () => { + * // we can access and modify `x` here + * let newValue = x.get() + otherField.toBigInt(); + * x.set(newValue); + * + * // ... + * }); + * + * // throws an error! + * x.get(); + * } + * ``` + */ class Unconstrained { private option: | { isSome: true; value: T } @@ -91,11 +540,11 @@ class Unconstrained { * Note: Can only be called outside provable code. */ get(): T { - if (inCheckedComputation() && !Snarky.bn254.run.inProverBlock()) + if (inCheckedComputation() && !Snarky.run.inProverBlock()) throw Error(`You cannot use Unconstrained.get() in provable code. -The only place where you can read unconstrained values is in Provable.witness() -and Provable.asProver() blocks, which execute outside the proof. +The only place where you can read unconstrained values is in ProvableBn254.witness() +and ProvableBn254.asProver() blocks, which execute outside the proof. `); assert(this.option.isSome, 'Empty `Unconstrained`'); // never triggered return this.option.value; @@ -123,7 +572,7 @@ and Provable.asProver() blocks, which execute outside the proof. * Something like the following should be used instead: * * ```ts - * let xWrapped = Unconstrained.witness(() => Provable.toConstant(type, x)); + * let xWrapped = Unconstrained.witness(() => ProvableBn254.toConstant(type, x)); * ``` */ static from(value: T) { @@ -164,3 +613,126 @@ and Provable.asProver() blocks, which execute outside the proof. toInput: () => ({}), }; } + +let primitives = new Set([FieldBn254, BoolBn254]); +function isPrimitive(obj: any) { + for (let P of primitives) { + if (obj instanceof P) return true; + } + return false; +} + +function cloneCircuitValue(obj: T): T { + // primitive JS types and functions aren't cloned + if (typeof obj !== 'object' || obj === null) return obj; + + // classes that define clone() are cloned using that method + if (obj.constructor !== undefined && 'clone' in obj.constructor) { + return (obj as any).constructor.clone(obj); + } + + // built-in JS datatypes with custom cloning strategies + if (Array.isArray(obj)) return obj.map(cloneCircuitValue) as any as T; + if (obj instanceof Set) + return new Set([...obj].map(cloneCircuitValue)) as any as T; + if (obj instanceof Map) + return new Map( + [...obj].map(([k, v]) => [k, cloneCircuitValue(v)]) + ) as any as T; + if (ArrayBuffer.isView(obj)) return new (obj.constructor as any)(obj); + + // o1js primitives and proofs aren't cloned + if (isPrimitive(obj)) { + return obj; + } + if (obj instanceof Proof) { + return obj; + } + + // cloning strategy that works for plain objects AND classes whose constructor only assigns properties + let propertyDescriptors: Record = {}; + for (let [key, value] of Object.entries(obj)) { + propertyDescriptors[key] = { + value: cloneCircuitValue(value), + writable: true, + enumerable: true, + configurable: true, + }; + } + return Object.create(Object.getPrototypeOf(obj), propertyDescriptors); +} + +function circuitValueEquals(a: T, b: T): boolean { + // primitive JS types and functions are checked for exact equality + if ( + typeof a !== 'object' || + a === null || + typeof b !== 'object' || + b === null + ) + return a === b; + + // built-in JS datatypes with custom equality checks + if (Array.isArray(a)) { + return ( + Array.isArray(b) && + a.length === b.length && + a.every((a_, i) => circuitValueEquals(a_, b[i])) + ); + } + if (a instanceof Set) { + return ( + b instanceof Set && a.size === b.size && [...a].every((a_) => b.has(a_)) + ); + } + if (a instanceof Map) { + return ( + b instanceof Map && + a.size === b.size && + [...a].every(([k, v]) => circuitValueEquals(v, b.get(k))) + ); + } + if (ArrayBuffer.isView(a) && !(a instanceof DataView)) { + // typed array + return ( + ArrayBuffer.isView(b) && + !(b instanceof DataView) && + circuitValueEquals([...(a as any)], [...(b as any)]) + ); + } + + // the two checks below cover o1js primitives and CircuitValues + // if we have an .equals method, try to use it + if ('equals' in a && typeof (a as any).equals === 'function') { + let isEqual = (a as any).equals(b).toBoolean(); + if (typeof isEqual === 'boolean') return isEqual; + if (isEqual instanceof BoolBn254) return isEqual.toBoolean(); + } + // if we have a .toFields method, try to use it + if ( + 'toFields' in a && + typeof (a as any).toFields === 'function' && + 'toFields' in b && + typeof (b as any).toFields === 'function' + ) { + let aFields = (a as any).toFields() as FieldBn254[]; + let bFields = (b as any).toFields() as FieldBn254[]; + return aFields.every((a, i) => a.equals(bFields[i]).toBoolean()); + } + + // equality test that works for plain objects AND classes whose constructor only assigns properties + let aEntries = Object.entries(a as any).filter(([, v]) => v !== undefined); + let bEntries = Object.entries(b as any).filter(([, v]) => v !== undefined); + if (aEntries.length !== bEntries.length) return false; + return aEntries.every( + ([key, value]) => key in b && circuitValueEquals((b as any)[key], value) + ); +} + +function toConstant(type: FlexibleProvableBn254, value: T): T; +function toConstant(type: ProvableBn254, value: T): T { + return type.fromFields( + type.toFields(value).map((x) => x.toConstant()), + type.toAuxiliary(value) + ); +} diff --git a/src/lib/field-bn254.ts b/src/lib/field-bn254.ts index 295878e92b..60dd2f2ee1 100644 --- a/src/lib/field-bn254.ts +++ b/src/lib/field-bn254.ts @@ -392,7 +392,7 @@ class FieldBn254 { isEven() { if (this.isConstant()) return new BoolBn254(this.toBigInt() % 2n === 0n); - let [, isOddVar, xDiv2Var] = Snarky.exists(2, () => { + let [, isOddVar, xDiv2Var] = Snarky.bn254.exists(2, () => { let bits = Fp.toBits(this.toBigInt()); let isOdd = bits.shift()! ? 1n : 0n; @@ -447,7 +447,7 @@ class FieldBn254 { return new FieldBn254(z); } // create a new witness for z = x*y - let z = Snarky.existsVar(() => + let z = Snarky.bn254.existsVar(() => FieldConst.fromBigint(Fp.mul(this.toBigInt(), toFp(y))) ); // add a multiplication constraint @@ -479,7 +479,7 @@ class FieldBn254 { return new FieldBn254(z); } // create a witness for z = x^(-1) - let z = Snarky.existsVar(() => { + let z = Snarky.bn254.existsVar(() => { let z = Fp.inverse(this.toBigInt()) ?? 0n; return FieldConst.fromBigint(z); }); @@ -546,7 +546,7 @@ class FieldBn254 { return new FieldBn254(Fp.square(this.toBigInt())); } // create a new witness for z = x^2 - let z = Snarky.existsVar(() => + let z = Snarky.bn254.existsVar(() => FieldConst.fromBigint(Fp.square(this.toBigInt())) ); // add a squaring constraint @@ -581,7 +581,7 @@ class FieldBn254 { return new FieldBn254(z); } // create a witness for sqrt(x) - let z = Snarky.existsVar(() => { + let z = Snarky.bn254.existsVar(() => { let z = Fp.sqrt(this.toBigInt()) ?? 0n; return FieldConst.fromBigint(z); }); @@ -599,7 +599,7 @@ class FieldBn254 { } // create witnesses z = 1/x, or z=0 if x=0, // and b = 1 - zx - let [, b, z] = Snarky.exists(2, () => { + let [, b, z] = Snarky.bn254.exists(2, () => { let x = this.toBigInt(); let z = Fp.inverse(x) ?? 0n; let b = Fp.sub(1n, Fp.mul(z, x)); @@ -640,7 +640,7 @@ class FieldBn254 { return this.sub(y).isZero(); } // if both are variables, we create one new variable for x-y so that `isZero` doesn't create two - let xMinusY = Snarky.existsVar(() => + let xMinusY = Snarky.bn254.existsVar(() => FieldConst.fromBigint(Fp.sub(this.toBigInt(), toFp(y))) ); Snarky.bn254.field.assertEqual(this.sub(y).value, xMinusY); diff --git a/src/lib/gadgets/basic-bn254.ts b/src/lib/gadgets/basic-bn254.ts new file mode 100644 index 0000000000..5b177cc71b --- /dev/null +++ b/src/lib/gadgets/basic-bn254.ts @@ -0,0 +1,134 @@ +/** + * Basic gadgets that only use generic gates + */ +import { Fp } from '../../bindings/crypto/finite-field.js'; +import type { FieldBn254, VarField } from '../field-bn254.js'; +import { existsOne, toVar } from './common-bn254.js'; +import { GatesBn254 } from '../gates-bn254.js'; +import { TupleN } from '../util/types.js'; +import { Snarky } from '../../snarky.js'; + +export { assertBoolean, arrayGet, assertOneOf }; + +/** + * Assert that x is either 0 or 1. + */ +function assertBoolean(x: VarField) { + Snarky.bn254.field.assertBoolean(x.value); +} + +// TODO: create constant versions of these and expose on Gadgets + +/** + * Get value from array in O(n) rows. + * + * Assumes that index is in [0, n), returns an unconstrained result otherwise. + * + * Note: This saves 0.5*n constraints compared to equals() + switch() + */ +function arrayGet(array: FieldBn254[], index: FieldBn254) { + let i = toVar(index); + + // witness result + let a = existsOne(() => array[Number(i.toBigInt())].toBigInt()); + + // we prove a === array[j] + z[j]*(i - j) for some z[j], for all j. + // setting j = i, this implies a === array[i] + // thanks to our assumption that the index i is within bounds, we know that j = i for some j + let n = array.length; + for (let j = 0; j < n; j++) { + let zj = existsOne(() => { + let zj = Fp.div( + Fp.sub(a.toBigInt(), array[j].toBigInt()), + Fp.sub(i.toBigInt(), Fp.fromNumber(j)) + ); + return zj ?? 0n; + }); + // prove that z[j]*(i - j) === a - array[j] + // TODO abstract this logic into a general-purpose assertMul() gadget, + // which is able to use the constant coefficient + // (snarky's assert_r1cs somehow leads to much more constraints than this) + if (array[j].isConstant()) { + // zj*i + (-j)*zj + 0*i + array[j] === a + assertBilinear(zj, i, [1n, -BigInt(j), 0n, array[j].toBigInt()], a); + } else { + let aMinusAj = toVar(a.sub(array[j])); + // zj*i + (-j)*zj + 0*i + 0 === (a - array[j]) + assertBilinear(zj, i, [1n, -BigInt(j), 0n, 0n], aMinusAj); + } + } + + return a; +} + +/** + * Assert that a value equals one of a finite list of constants: + * `(x - c1)*(x - c2)*...*(x - cn) === 0` + * + * TODO: what prevents us from getting the same efficiency with snarky DSL code? + */ +function assertOneOf(x: FieldBn254, allowed: [bigint, bigint, ...bigint[]]) { + let xv = toVar(x); + let [c1, c2, ...c] = allowed; + let n = c.length; + if (n === 0) { + // (x - c1)*(x - c2) === 0 + assertBilinear(xv, xv, [1n, -(c1 + c2), 0n, c1 * c2]); + return; + } + // z = (x - c1)*(x - c2) + let z = bilinear(xv, xv, [1n, -(c1 + c2), 0n, c1 * c2]); + + for (let i = 0; i < n; i++) { + if (i < n - 1) { + // z = z*(x - c) + z = bilinear(z, xv, [1n, -c[i], 0n, 0n]); + } else { + // z*(x - c) === 0 + assertBilinear(z, xv, [1n, -c[i], 0n, 0n]); + } + } +} + +// low-level helpers to create generic gates + +/** + * Compute bilinear function of x and y: + * `z = a*x*y + b*x + c*y + d` + */ +function bilinear(x: VarField, y: VarField, [a, b, c, d]: TupleN) { + let z = existsOne(() => { + let x0 = x.toBigInt(); + let y0 = y.toBigInt(); + return a * x0 * y0 + b * x0 + c * y0 + d; + }); + // b*x + c*y - z + a*x*y + d === 0 + GatesBn254.generic( + { left: b, right: c, out: -1n, mul: a, const: d }, + { left: x, right: y, out: z } + ); + return z; +} + +/** + * Assert bilinear equation on x, y and z: + * `a*x*y + b*x + c*y + d === z` + * + * The default for z is 0. + */ +function assertBilinear( + x: VarField, + y: VarField, + [a, b, c, d]: TupleN, + z?: VarField +) { + // b*x + c*y - z? + a*x*y + d === 0 + GatesBn254.generic( + { left: b, right: c, out: z === undefined ? 0n : -1n, mul: a, const: d }, + { left: x, right: y, out: z === undefined ? emptyCell() : z } + ); +} + +function emptyCell() { + return existsOne(() => 0n); +} diff --git a/src/lib/gadgets/bit-slices-bn254.ts b/src/lib/gadgets/bit-slices-bn254.ts new file mode 100644 index 0000000000..a52787e0da --- /dev/null +++ b/src/lib/gadgets/bit-slices-bn254.ts @@ -0,0 +1,156 @@ +/** + * Gadgets for converting between field elements and bit slices of various lengths + */ +import { bigIntToBits } from '../../bindings/crypto/bigint-helpers.js'; +import { BoolBn254 } from '../bool-bn254.js'; +import { FieldBn254 } from '../field-bn254.js'; +import { UInt8 } from '../int.js'; +import { ProvableBn254 } from '../provable-bn254.js'; +import { chunk } from '../util/arrays.js'; +import { assert, exists } from './common-bn254.js'; +import type { Field3 } from './foreign-field-bn254.js'; +import { l } from './range-check-bn254.js'; + +export { bytesToWord, wordToBytes, wordsToBytes, bytesToWords, sliceField3 }; + +// conversion between bytes and multi-byte words + +/** + * Convert an array of UInt8 to a FieldBn254 element. Expects little endian representation. + */ +function bytesToWord(wordBytes: UInt8[]): FieldBn254 { + return wordBytes.reduce((acc, byte, idx) => { + const shift = 1n << BigInt(8 * idx); + return acc.add(byte.value.mul(shift)); + }, FieldBn254.from(0)); +} + +/** + * Convert a FieldBn254 element to an array of UInt8. Expects little endian representation. + * @param bytesPerWord number of bytes per word + */ +function wordToBytes(word: FieldBn254, bytesPerWord = 8): UInt8[] { + let bytes = ProvableBn254.witness(ProvableBn254.Array(UInt8, bytesPerWord), () => { + let w = word.toBigInt(); + return Array.from({ length: bytesPerWord }, (_, k) => + UInt8.from((w >> BigInt(8 * k)) & 0xffn) + ); + }); + + // check decomposition + bytesToWord(bytes).assertEquals(word); + + return bytes; +} + +/** + * Convert an array of FieldBn254 elements to an array of UInt8. Expects little endian representation. + * @param bytesPerWord number of bytes per word + */ +function wordsToBytes(words: FieldBn254[], bytesPerWord = 8): UInt8[] { + return words.flatMap((w) => wordToBytes(w, bytesPerWord)); +} +/** + * Convert an array of UInt8 to an array of FieldBn254 elements. Expects little endian representation. + * @param bytesPerWord number of bytes per word + */ +function bytesToWords(bytes: UInt8[], bytesPerWord = 8): FieldBn254[] { + return chunk(bytes, bytesPerWord).map(bytesToWord); +} + +// conversion between 3-limb foreign fields and arbitrary bit slices + +/** + * ProvableBn254 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) + */ +function sliceField3( + [x0, x1, x2]: Field3, + { maxBits, chunkSize }: { maxBits: number; chunkSize: number } +) { + let l_ = Number(l); + assert(maxBits <= 3 * l_, `expected max bits <= 3*${l_}, got ${maxBits}`); + + // first limb + let result0 = sliceField(x0, Math.min(l_, maxBits), chunkSize); + if (maxBits <= l_) return result0.chunks; + maxBits -= l_; + + // second limb + let result1 = sliceField(x1, Math.min(l_, maxBits), chunkSize, result0); + if (maxBits <= l_) return result0.chunks.concat(result1.chunks); + maxBits -= l_; + + // third limb + let result2 = sliceField(x2, maxBits, chunkSize, result1); + return result0.chunks.concat(result1.chunks, result2.chunks); +} + +/** + * ProvableBn254 method for slicing a field element into smaller bit chunks of length `chunkSize`. + * + * This serves as a range check that the input is in [0, 2^maxBits) + * + * If `chunkSize` does not divide `maxBits`, the last chunk will be smaller. + * We return the number of free bits in the last chunk, and optionally accept such a result from a previous call, + * so that this function can be used to slice up a bigint of multiple limbs into homogeneous chunks. + * + * TODO: atm this uses expensive boolean checks for each bit. + * For larger chunks, we should use more efficient range checks. + */ +function sliceField( + x: FieldBn254, + maxBits: number, + chunkSize: number, + leftover?: { chunks: FieldBn254[]; leftoverSize: number } +) { + let bits = exists(maxBits, () => { + let bits = bigIntToBits(x.toBigInt()); + // normalize length + if (bits.length > maxBits) bits = bits.slice(0, maxBits); + if (bits.length < maxBits) + bits = bits.concat(Array(maxBits - bits.length).fill(false)); + return bits.map(BigInt); + }); + + let chunks = []; + let sum = FieldBn254.from(0n); + + // if there's a leftover chunk from a previous sliceField() call, we complete it + if (leftover !== undefined) { + let { chunks: previous, leftoverSize: size } = leftover; + let remainingChunk = FieldBn254.from(0n); + for (let i = 0; i < size; i++) { + let bit = bits[i]; + BoolBn254.check(BoolBn254.Unsafe.ofField(bit)); + remainingChunk = remainingChunk.add(bit.mul(1n << BigInt(i))); + } + sum = remainingChunk = remainingChunk.seal(); + let chunk = previous[previous.length - 1]; + previous[previous.length - 1] = chunk.add( + remainingChunk.mul(1n << BigInt(chunkSize - size)) + ); + } + + let i = leftover?.leftoverSize ?? 0; + for (; i < maxBits; i += chunkSize) { + // prove that chunk has `chunkSize` bits + // TODO: this inner sum should be replaced with a more efficient range check when possible + let chunk = FieldBn254.from(0n); + let size = Math.min(maxBits - i, chunkSize); // last chunk might be smaller + for (let j = 0; j < size; j++) { + let bit = bits[i + j]; + BoolBn254.check(BoolBn254.Unsafe.ofField(bit)); + chunk = chunk.add(bit.mul(1n << BigInt(j))); + } + chunk = chunk.seal(); + // prove that chunks add up to x + sum = sum.add(chunk.mul(1n << BigInt(i))); + chunks.push(chunk); + } + sum.assertEquals(x); + + let leftoverSize = i - maxBits; + return { chunks, leftoverSize } as const; +} diff --git a/src/lib/gadgets/elliptic-curve-bn254.ts b/src/lib/gadgets/elliptic-curve-bn254.ts index cc3d8fcfe8..d131a33d78 100644 --- a/src/lib/gadgets/elliptic-curve-bn254.ts +++ b/src/lib/gadgets/elliptic-curve-bn254.ts @@ -17,8 +17,8 @@ import { import { BoolBn254 } from '../bool-bn254.js'; import { provable } from '../circuit-value-bn254.js'; import { assertPositiveInteger } from '../../bindings/crypto/non-negative.js'; -import { arrayGet, assertBoolean } from './basic.js'; -import { sliceField3 } from './bit-slices.js'; +import { arrayGet, assertBoolean } from './basic-bn254.js'; +import { sliceField3 } from './bit-slices-bn254.js'; import { Hashed } from '../provable-types/packed.js'; // external API diff --git a/src/lib/gadgets/foreign-field-bn254.ts b/src/lib/gadgets/foreign-field-bn254.ts index 4f6d0b9cb7..fb11dcab74 100644 --- a/src/lib/gadgets/foreign-field-bn254.ts +++ b/src/lib/gadgets/foreign-field-bn254.ts @@ -10,9 +10,9 @@ import { BoolBn254 } from '../bool-bn254.js'; import { Unconstrained } from '../circuit-value-bn254.js'; import { FieldBn254 } from '../field-bn254.js'; import { GatesBn254, foreignFieldAdd } from '../gates-bn254.js'; -import { modifiedField } from '../provable-types/fields.js'; +import { modifiedField } from '../provable-types/fields-bn254.js'; import { Tuple, TupleN } from '../util/types.js'; -import { assertOneOf } from './basic.js'; +import { assertOneOf } from './basic-bn254.js'; import { assert, bitSlice, exists, toVar, toVars } from './common-bn254.js'; import { l, diff --git a/src/lib/hash-bn254.ts b/src/lib/hash-bn254.ts new file mode 100644 index 0000000000..d4bc3d3441 --- /dev/null +++ b/src/lib/hash-bn254.ts @@ -0,0 +1,247 @@ +import { HashInput, ProvableExtendedBn254, Struct } from './circuit-value-bn254.js'; +import { Snarky } from '../snarky.js'; +import { FieldBn254 } from './core-bn254.js'; +import { createHashHelpers } from './hash-generic.js'; +import { ProvableBn254 } from './provable-bn254.js'; +import { MlFieldArray } from './ml/fields-bn254.js'; +import { Poseidon as PoseidonBigint } from '../bindings/crypto/poseidon.js'; +import { assert } from './errors.js'; +import { rangeCheckN } from './gadgets/range-check-bn254.js'; +import { TupleN } from './util/types.js'; + +// external API +export { Poseidon, TokenSymbol }; + +// internal API +export { + ProvableHashable, + HashInput, + HashHelpers, + emptyHashWithPrefix, + hashWithPrefix, + salt, + packToFields, + emptyReceiptChainHash, + hashConstant, + isHashable, +}; + +type Hashable = { toInput: (x: T) => HashInput; empty: () => T }; +type ProvableHashable = ProvableBn254 & Hashable; + +class Sponge { + #sponge: unknown; + + constructor() { + let isChecked = ProvableBn254.inCheckedComputation(); + this.#sponge = Snarky.poseidon.sponge.create(isChecked); + } + + absorb(x: FieldBn254) { + Snarky.poseidon.sponge.absorb(this.#sponge, x.value); + } + + squeeze(): FieldBn254 { + return FieldBn254(Snarky.poseidon.sponge.squeeze(this.#sponge)); + } +} + +const Poseidon = { + hash(input: FieldBn254[]) { + if (isConstant(input)) { + return FieldBn254(PoseidonBigint.hash(toBigints(input))); + } + return Poseidon.update(Poseidon.initialState(), input)[0]; + }, + + update(state: [FieldBn254, FieldBn254, FieldBn254], input: FieldBn254[]) { + if (isConstant(state) && isConstant(input)) { + let newState = PoseidonBigint.update(toBigints(state), toBigints(input)); + return TupleN.fromArray(3, newState.map(FieldBn254)); + } + + let newState = Snarky.poseidon.update( + MlFieldArray.to(state), + MlFieldArray.to(input) + ); + return MlFieldArray.from(newState) as [FieldBn254, FieldBn254, FieldBn254]; + }, + + hashWithPrefix(prefix: string, input: FieldBn254[]) { + let init = Poseidon.update(Poseidon.initialState(), [ + prefixToField(prefix), + ]); + return Poseidon.update(init, input)[0]; + }, + + initialState(): [FieldBn254, FieldBn254, FieldBn254] { + return [FieldBn254(0), FieldBn254(0), FieldBn254(0)]; + }, + + hashToGroup(input: FieldBn254[]) { + if (isConstant(input)) { + let result = PoseidonBigint.hashToGroup(toBigints(input)); + assert(result !== undefined, 'hashToGroup works on all inputs'); + let { x, y } = result; + return { + x: FieldBn254(x), + y: { x0: FieldBn254(y.x0), x1: FieldBn254(y.x1) }, + }; + } + + // y = sqrt(y^2) + let [, xv, yv] = Snarky.poseidon.hashToGroup(MlFieldArray.to(input)); + + let x = FieldBn254(xv); + let y = FieldBn254(yv); + + let x0 = ProvableBn254.witness(FieldBn254, () => { + // the even root of y^2 will become x0, so the APIs are uniform + let isEven = y.toBigInt() % 2n === 0n; + + // we just change the order so the even root is x0 + // y.mul(-1); is the second root of sqrt(y^2) + return isEven ? y : y.mul(-1); + }); + + let x1 = x0.mul(-1); + + // we check that either x0 or x1 match the original root y + y.equals(x0).or(y.equals(x1)).assertTrue(); + + return { x, y: { x0, x1 } }; + }, + + /** + * Hashes a provable type efficiently. + * + * ```ts + * let skHash = Poseidon.hashPacked(PrivateKey, secretKey); + * ``` + * + * Note: Instead of just doing `Poseidon.hash(value.toFields())`, this + * uses the `toInput()` method on the provable type to pack the input into as few + * field elements as possible. This saves constraints because packing has a much + * lower per-field element cost than hashing. + */ + hashPacked(type: Hashable, value: T) { + let input = type.toInput(value); + let packed = packToFields(input); + return Poseidon.hash(packed); + }, + + Sponge, +}; + +function hashConstant(input: FieldBn254[]) { + return FieldBn254(PoseidonBigint.hash(toBigints(input))); +} + +const HashHelpers = createHashHelpers(FieldBn254, Poseidon); +let { salt, emptyHashWithPrefix, hashWithPrefix } = HashHelpers; + +// same as Random_oracle.prefix_to_field in OCaml +function prefixToField(prefix: string) { + if (prefix.length * 8 >= 255) throw Error('prefix too long'); + let bits = [...prefix] + .map((char) => { + // convert char to 8 bits + let bits = []; + for (let j = 0, c = char.charCodeAt(0); j < 8; j++, c >>= 1) { + bits.push(!!(c & 1)); + } + return bits; + }) + .flat(); + return FieldBn254.fromBits(bits); +} + +/** + * Convert the {fields, packed} hash input representation to a list of field elements + * Random_oracle_input.Chunked.pack_to_fields + */ +function packToFields({ fields = [], packed = [] }: HashInput) { + if (packed.length === 0) return fields; + let packedBits = []; + let currentPackedField = FieldBn254(0); + let currentSize = 0; + for (let [field, size] of packed) { + currentSize += size; + if (currentSize < 255) { + currentPackedField = currentPackedField + .mul(FieldBn254(1n << BigInt(size))) + .add(field); + } else { + packedBits.push(currentPackedField); + currentSize = size; + currentPackedField = field; + } + } + packedBits.push(currentPackedField); + return fields.concat(packedBits); +} + +function isHashable(obj: any): obj is Hashable { + if (!obj) { + return false; + } + const hasToInput = 'toInput' in obj && typeof obj.toInput === 'function'; + const hasEmpty = 'empty' in obj && typeof obj.empty === 'function'; + return hasToInput && hasEmpty; +} + +const TokenSymbolPure: ProvableExtendedBn254< + { symbol: string; field: FieldBn254 }, + string +> = { + toFields({ field }) { + return [field]; + }, + toAuxiliary(value) { + return [value?.symbol ?? '']; + }, + fromFields([field], [symbol]) { + return { symbol, field }; + }, + sizeInFields() { + return 1; + }, + check({ field }: TokenSymbol) { + rangeCheckN(48, field); + }, + toJSON({ symbol }) { + return symbol; + }, + fromJSON(symbol: string) { + let field = prefixToField(symbol); + return { symbol, field }; + }, + toInput({ field }) { + return { packed: [[field, 48]] }; + }, + empty() { + return { symbol: '', field: FieldBn254(0n) }; + }, +}; +class TokenSymbol extends Struct(TokenSymbolPure) { + static from(symbol: string): TokenSymbol { + let bytesLength = new TextEncoder().encode(symbol).length; + if (bytesLength > 6) + throw Error( + `Token symbol ${symbol} should be a maximum of 6 bytes, but is ${bytesLength}` + ); + let field = prefixToField(symbol); + return { symbol, field }; + } +} + +function emptyReceiptChainHash() { + return emptyHashWithPrefix('CodaReceiptEmpty'); +} + +function isConstant(fields: FieldBn254[]) { + return fields.every((x) => x.isConstant()); +} +function toBigints(fields: FieldBn254[]) { + return fields.map((x) => x.toBigInt()); +} diff --git a/src/lib/provable-types/packed-bn254.ts b/src/lib/provable-types/packed-bn254.ts new file mode 100644 index 0000000000..059d2d31a1 --- /dev/null +++ b/src/lib/provable-types/packed-bn254.ts @@ -0,0 +1,271 @@ +import { provableFromClass } from '../../bindings/lib/provable-snarky-bn254.js'; +import { + HashInput, + ProvableExtendedBn254, + Unconstrained, +} from '../circuit-value-bn254.js'; +import { FieldBn254 } from '../field-bn254.js'; +import { assert } from '../gadgets/common-bn254.js'; +import { Poseidon, ProvableHashable, packToFields } from '../hash.js'; +import { Provable } from '../provable.js'; +import { fields, modifiedField } from './fields.js'; + +export { Packed, Hashed }; + +/** + * `Packed` is a "packed" representation of any type `T`. + * + * "Packed" means that field elements which take up fewer than 254 bits are packed together into + * as few field elements as possible. + * + * For example, you can pack several Bools (1 bit) or UInt32s (32 bits) into a single field element. + * + * Using a packed representation can make sense in provable code where the number of constraints + * depends on the number of field elements per value. + * + * For example, `Provable.if(bool, x, y)` takes O(n) constraints, where n is the number of field + * elements in x and y. + * + * Usage: + * + * ```ts + * // define a packed type from a type + * let PackedType = Packed.create(MyType); + * + * // pack a value + * let packed = PackedType.pack(value); + * + * // ... operations on packed values, more efficient than on plain values ... + * + * // unpack a value + * let value = packed.unpack(); + * ``` + * + * **Warning**: Packing only makes sense where packing actually reduces the number of field elements. + * For example, it doesn't make sense to pack a _single_ Bool, because it will be 1 field element before + * and after packing. On the other hand, it does makes sense to pack a type that holds 10 or 20 Bools. + */ +class Packed { + packed: FieldBn254[]; + value: Unconstrained; + + /** + * Create a packed representation of `type`. You can then use `PackedType.pack(x)` to pack a value. + */ + static create(type: ProvableExtendedBn254): typeof Packed & { + provable: ProvableHashable>; + } { + // compute size of packed representation + let input = type.toInput(type.empty()); + let packedSize = countFields(input); + + return class Packed_ extends Packed { + static _innerProvable = type; + static _provable = provableFromClass(Packed_, { + packed: fields(packedSize), + value: Unconstrained.provable, + }) as ProvableHashable>; + + static empty(): Packed { + return Packed_.pack(type.empty()); + } + + static get provable() { + assert(this._provable !== undefined, 'Packed not initialized'); + return this._provable; + } + }; + } + + constructor(packed: FieldBn254[], value: Unconstrained) { + this.packed = packed; + this.value = value; + } + + /** + * Pack a value. + */ + static pack(x: T): Packed { + let type = this.innerProvable; + let input = type.toInput(x); + let packed = packToFields(input); + let unconstrained = Unconstrained.witness(() => + Provable.toConstant(type, x) + ); + return new this(packed, unconstrained); + } + + /** + * Unpack a value. + */ + unpack(): T { + let value = Provable.witness(this.Constructor.innerProvable, () => + this.value.get() + ); + + // prove that the value packs to the packed fields + let input = this.Constructor.innerProvable.toInput(value); + let packed = packToFields(input); + for (let i = 0; i < this.packed.length; i++) { + this.packed[i].assertEquals(packed[i]); + } + + return value; + } + + toFields(): FieldBn254[] { + return this.packed; + } + + // dynamic subclassing infra + static _provable: ProvableHashable> | undefined; + static _innerProvable: ProvableExtendedBn254 | undefined; + + get Constructor(): typeof Packed { + return this.constructor as typeof Packed; + } + + static get innerProvable(): ProvableExtendedBn254 { + assert(this._innerProvable !== undefined, 'Packed not initialized'); + return this._innerProvable; + } +} + +function countFields(input: HashInput) { + let n = input.fields?.length ?? 0; + let pendingBits = 0; + + for (let [, bits] of input.packed ?? []) { + pendingBits += bits; + if (pendingBits >= FieldBn254.sizeInBits) { + n++; + pendingBits = bits; + } + } + if (pendingBits > 0) n++; + + return n; +} + +/** + * `Hashed` represents a type `T` by its hash. + * + * Since a hash is only a single field element, this can be more efficient in provable code + * where the number of constraints depends on the number of field elements per value. + * + * For example, `Provable.if(bool, x, y)` takes O(n) constraints, where n is the number of field + * elements in x and y. With Hashed, this is reduced to O(1). + * + * The downside is that you will pay the overhead of hashing your values, so it helps to experiment + * in which parts of your code a hashed representation is beneficial. + * + * Usage: + * + * ```ts + * // define a hashed type from a type + * let HashedType = Hashed.create(MyType); + * + * // hash a value + * let hashed = HashedType.hash(value); + * + * // ... operations on hashes, more efficient than on plain values ... + * + * // unhash to get the original value + * let value = hashed.unhash(); + * ``` + */ +class Hashed { + hash: FieldBn254; + value: Unconstrained; + + /** + * Create a hashed representation of `type`. You can then use `HashedType.hash(x)` to wrap a value in a `Hashed`. + */ + static create( + type: ProvableHashable, + hash?: (t: T) => FieldBn254 + ): typeof Hashed & { + provable: ProvableHashable>; + } { + let _hash = hash ?? ((t: T) => Poseidon.hashPacked(type, t)); + + let dummyHash = _hash(type.empty()); + + return class Hashed_ extends Hashed { + static _innerProvable = type; + static _provable = provableFromClass(Hashed_, { + hash: modifiedField({ empty: () => dummyHash }), + value: Unconstrained.provable, + }) as ProvableHashable>; + + static _hash = _hash satisfies (t: T) => FieldBn254; + + static empty(): Hashed { + return new this(dummyHash, Unconstrained.from(type.empty())); + } + + static get provable() { + assert(this._provable !== undefined, 'Hashed not initialized'); + return this._provable; + } + }; + } + + constructor(hash: FieldBn254, value: Unconstrained) { + this.hash = hash; + this.value = value; + } + + static _hash(_: any): FieldBn254 { + assert(false, 'Hashed not initialized'); + } + + /** + * Wrap a value, and represent it by its hash in provable code. + * + * ```ts + * let hashed = HashedType.hash(value); + * ``` + * + * Optionally, if you already have the hash, you can pass it in and avoid recomputing it. + */ + static hash(value: T, hash?: FieldBn254): Hashed { + hash ??= this._hash(value); + let unconstrained = Unconstrained.witness(() => + Provable.toConstant(this.innerProvable, value) + ); + return new this(hash, unconstrained); + } + + /** + * Unwrap a value from its hashed variant. + */ + unhash(): T { + let value = Provable.witness(this.Constructor.innerProvable, () => + this.value.get() + ); + + // prove that the value hashes to the hash + let hash = this.Constructor._hash(value); + this.hash.assertEquals(hash); + + return value; + } + + toFields(): FieldBn254[] { + return [this.hash]; + } + + // dynamic subclassing infra + static _provable: ProvableHashable> | undefined; + static _innerProvable: ProvableHashable | undefined; + + get Constructor(): typeof Hashed { + return this.constructor as typeof Hashed; + } + + static get innerProvable(): ProvableHashable { + assert(this._innerProvable !== undefined, 'Hashed not initialized'); + return this._innerProvable; + } +} From bcf6af9dbd709733aba4ea2a894a2a51ee21d337 Mon Sep 17 00:00:00 2001 From: gabrielbosio Date: Tue, 12 Mar 2024 17:41:45 -0300 Subject: [PATCH 03/17] Update mina --- src/mina | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mina b/src/mina index 716f8eefff..eaeef73e1c 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit 716f8eefff86d00456a64f5a8c748bfb45b0a507 +Subproject commit eaeef73e1cbe050e09e6c2957729d4c566d3984c From 145a9622b5e06060cc4ae973d9ab07c0a73871e7 Mon Sep 17 00:00:00 2001 From: gabrielbosio Date: Tue, 12 Mar 2024 17:41:56 -0300 Subject: [PATCH 04/17] Adapt to bn254 poseidon --- src/bindings | 2 +- src/lib/gadgets/elliptic-curve-bn254.ts | 2 +- src/lib/hash-bn254.ts | 2 +- src/lib/provable-types/packed-bn254.ts | 18 +++++++++--------- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/bindings b/src/bindings index df9f2d6fa5..7522edbdc4 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit df9f2d6fa56d9589eae3a7ace11cd10e7796ae26 +Subproject commit 7522edbdc42b738f95e22e22f89f5515a4fdbb9f diff --git a/src/lib/gadgets/elliptic-curve-bn254.ts b/src/lib/gadgets/elliptic-curve-bn254.ts index d131a33d78..139549dfc8 100644 --- a/src/lib/gadgets/elliptic-curve-bn254.ts +++ b/src/lib/gadgets/elliptic-curve-bn254.ts @@ -19,7 +19,7 @@ import { provable } from '../circuit-value-bn254.js'; import { assertPositiveInteger } from '../../bindings/crypto/non-negative.js'; import { arrayGet, assertBoolean } from './basic-bn254.js'; import { sliceField3 } from './bit-slices-bn254.js'; -import { Hashed } from '../provable-types/packed.js'; +import { Hashed } from '../provable-types/packed-bn254.js'; // external API export { EllipticCurveBn254, PointBn254, Ecdsa }; diff --git a/src/lib/hash-bn254.ts b/src/lib/hash-bn254.ts index d4bc3d3441..0b46a0df41 100644 --- a/src/lib/hash-bn254.ts +++ b/src/lib/hash-bn254.ts @@ -4,7 +4,7 @@ import { FieldBn254 } from './core-bn254.js'; import { createHashHelpers } from './hash-generic.js'; import { ProvableBn254 } from './provable-bn254.js'; import { MlFieldArray } from './ml/fields-bn254.js'; -import { Poseidon as PoseidonBigint } from '../bindings/crypto/poseidon.js'; +import { PoseidonBn254 as PoseidonBigint } from '../bindings/crypto/poseidon.js'; import { assert } from './errors.js'; import { rangeCheckN } from './gadgets/range-check-bn254.js'; import { TupleN } from './util/types.js'; diff --git a/src/lib/provable-types/packed-bn254.ts b/src/lib/provable-types/packed-bn254.ts index 059d2d31a1..adfbd7786b 100644 --- a/src/lib/provable-types/packed-bn254.ts +++ b/src/lib/provable-types/packed-bn254.ts @@ -6,9 +6,9 @@ import { } from '../circuit-value-bn254.js'; import { FieldBn254 } from '../field-bn254.js'; import { assert } from '../gadgets/common-bn254.js'; -import { Poseidon, ProvableHashable, packToFields } from '../hash.js'; -import { Provable } from '../provable.js'; -import { fields, modifiedField } from './fields.js'; +import { Poseidon, ProvableHashable, packToFields } from '../hash-bn254.js'; +import { ProvableBn254 } from '../provable-bn254.js'; +import { fields, modifiedField } from './fields-bn254.js'; export { Packed, Hashed }; @@ -23,7 +23,7 @@ export { Packed, Hashed }; * Using a packed representation can make sense in provable code where the number of constraints * depends on the number of field elements per value. * - * For example, `Provable.if(bool, x, y)` takes O(n) constraints, where n is the number of field + * For example, `ProvableBn254.if(bool, x, y)` takes O(n) constraints, where n is the number of field * elements in x and y. * * Usage: @@ -90,7 +90,7 @@ class Packed { let input = type.toInput(x); let packed = packToFields(input); let unconstrained = Unconstrained.witness(() => - Provable.toConstant(type, x) + ProvableBn254.toConstant(type, x) ); return new this(packed, unconstrained); } @@ -99,7 +99,7 @@ class Packed { * Unpack a value. */ unpack(): T { - let value = Provable.witness(this.Constructor.innerProvable, () => + let value = ProvableBn254.witness(this.Constructor.innerProvable, () => this.value.get() ); @@ -153,7 +153,7 @@ function countFields(input: HashInput) { * Since a hash is only a single field element, this can be more efficient in provable code * where the number of constraints depends on the number of field elements per value. * - * For example, `Provable.if(bool, x, y)` takes O(n) constraints, where n is the number of field + * For example, `ProvableBn254.if(bool, x, y)` takes O(n) constraints, where n is the number of field * elements in x and y. With Hashed, this is reduced to O(1). * * The downside is that you will pay the overhead of hashing your values, so it helps to experiment @@ -232,7 +232,7 @@ class Hashed { static hash(value: T, hash?: FieldBn254): Hashed { hash ??= this._hash(value); let unconstrained = Unconstrained.witness(() => - Provable.toConstant(this.innerProvable, value) + ProvableBn254.toConstant(this.innerProvable, value) ); return new this(hash, unconstrained); } @@ -241,7 +241,7 @@ class Hashed { * Unwrap a value from its hashed variant. */ unhash(): T { - let value = Provable.witness(this.Constructor.innerProvable, () => + let value = ProvableBn254.witness(this.Constructor.innerProvable, () => this.value.get() ); From 382b0eb64acd0a548b4cda5d028ea055af70df0f Mon Sep 17 00:00:00 2001 From: gabrielbosio Date: Tue, 12 Mar 2024 19:15:03 -0300 Subject: [PATCH 05/17] Use bn254 poseidon --- src/bindings | 2 +- src/lib/hash-bn254.ts | 8 ++++---- src/mina | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/bindings b/src/bindings index 7522edbdc4..ccbe3407fa 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 7522edbdc42b738f95e22e22f89f5515a4fdbb9f +Subproject commit ccbe3407fa7bf5bc79cb16abf51eb5bea50c0694 diff --git a/src/lib/hash-bn254.ts b/src/lib/hash-bn254.ts index 0b46a0df41..40d504622c 100644 --- a/src/lib/hash-bn254.ts +++ b/src/lib/hash-bn254.ts @@ -34,15 +34,15 @@ class Sponge { constructor() { let isChecked = ProvableBn254.inCheckedComputation(); - this.#sponge = Snarky.poseidon.sponge.create(isChecked); + this.#sponge = Snarky.bn254.poseidon.sponge.create(isChecked); } absorb(x: FieldBn254) { - Snarky.poseidon.sponge.absorb(this.#sponge, x.value); + Snarky.bn254.poseidon.sponge.absorb(this.#sponge, x.value); } squeeze(): FieldBn254 { - return FieldBn254(Snarky.poseidon.sponge.squeeze(this.#sponge)); + return FieldBn254(Snarky.bn254.poseidon.sponge.squeeze(this.#sponge)); } } @@ -60,7 +60,7 @@ const Poseidon = { return TupleN.fromArray(3, newState.map(FieldBn254)); } - let newState = Snarky.poseidon.update( + let newState = Snarky.bn254.poseidon.update( MlFieldArray.to(state), MlFieldArray.to(input) ); diff --git a/src/mina b/src/mina index eaeef73e1c..cce3d713af 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit eaeef73e1cbe050e09e6c2957729d4c566d3984c +Subproject commit cce3d713afb4da6baa10834f9b1fa70b4c8cea92 From e285fb4658a58647df2c2c02bbd369c96bb6886d Mon Sep 17 00:00:00 2001 From: gabrielbosio Date: Tue, 12 Mar 2024 19:22:39 -0300 Subject: [PATCH 06/17] Fix run issue and add comment in unused fn --- src/lib/circuit-value-bn254.ts | 2 +- src/lib/hash-bn254.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/circuit-value-bn254.ts b/src/lib/circuit-value-bn254.ts index 39c62699c2..3eb58a1652 100644 --- a/src/lib/circuit-value-bn254.ts +++ b/src/lib/circuit-value-bn254.ts @@ -540,7 +540,7 @@ class Unconstrained { * Note: Can only be called outside provable code. */ get(): T { - if (inCheckedComputation() && !Snarky.run.inProverBlock()) + if (inCheckedComputation() && !Snarky.bn254.run.inProverBlock()) throw Error(`You cannot use Unconstrained.get() in provable code. The only place where you can read unconstrained values is in ProvableBn254.witness() diff --git a/src/lib/hash-bn254.ts b/src/lib/hash-bn254.ts index 40d504622c..38989ae0af 100644 --- a/src/lib/hash-bn254.ts +++ b/src/lib/hash-bn254.ts @@ -89,6 +89,7 @@ const Poseidon = { }; } + // TODO: This is not used for the verifier circuit // y = sqrt(y^2) let [, xv, yv] = Snarky.poseidon.hashToGroup(MlFieldArray.to(input)); From d25e2499aae18b398b82fbb060ac4eb1b24c54e7 Mon Sep 17 00:00:00 2001 From: gabrielbosio Date: Wed, 13 Mar 2024 15:46:45 -0300 Subject: [PATCH 07/17] Update mina and bindings --- src/bindings | 2 +- src/mina | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bindings b/src/bindings index ccbe3407fa..7a84f6540a 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit ccbe3407fa7bf5bc79cb16abf51eb5bea50c0694 +Subproject commit 7a84f6540a2844097b333d4779b1722abb173120 diff --git a/src/mina b/src/mina index cce3d713af..059b613ba9 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit cce3d713afb4da6baa10834f9b1fa70b4c8cea92 +Subproject commit 059b613ba9ef9ff95368dc1be2f7da8e173443d9 From de8b5f62208c59eeb563ffc84c9642db55019b4c Mon Sep 17 00:00:00 2001 From: gabrielbosio Date: Wed, 13 Mar 2024 17:26:15 -0300 Subject: [PATCH 08/17] Return index with proof --- src/bindings | 2 +- src/mina | 2 +- src/snarky.d.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bindings b/src/bindings index 7a84f6540a..783c6d54e8 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 7a84f6540a2844097b333d4779b1722abb173120 +Subproject commit 783c6d54e898e13c12158f8df4c4fd80f376dfe6 diff --git a/src/mina b/src/mina index 059b613ba9..64ae6030e4 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit 059b613ba9ef9ff95368dc1be2f7da8e173443d9 +Subproject commit 64ae6030e40b6250505052dc5dd3a135c1c5f75d diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 508a8731b5..44d88615c3 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -887,7 +887,7 @@ declare const Snarky: { publicInputSize: number, publicInput: MlArray, keypair: Snarky.Bn254.Keypair - ): Snarky.Bn254.Proof; + ): string; /** * Verifies a proof using the public input, the proof and the verification key of the circuit. From 3278a3d1d923029f306de8fc36301073fd7e861b Mon Sep 17 00:00:00 2001 From: gabrielbosio Date: Wed, 13 Mar 2024 21:53:41 -0300 Subject: [PATCH 09/17] Update bindings and mina --- src/bindings | 2 +- src/mina | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bindings b/src/bindings index 783c6d54e8..c8277d2d8d 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 783c6d54e898e13c12158f8df4c4fd80f376dfe6 +Subproject commit c8277d2d8d7fffa7bb982a685ae139b0742fa807 diff --git a/src/mina b/src/mina index 64ae6030e4..a0dda4e114 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit 64ae6030e40b6250505052dc5dd3a135c1c5f75d +Subproject commit a0dda4e1142d40c99fcecd3a5103b5501d86ddcd From 02e38a5fc5c84d810afc6eb1acdabd56f3289f10 Mon Sep 17 00:00:00 2001 From: gabrielbosio Date: Wed, 13 Mar 2024 22:19:13 -0300 Subject: [PATCH 10/17] Implement serialize in bn254 proof --- src/bindings | 2 +- src/mina | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bindings b/src/bindings index c8277d2d8d..0016ebbf2e 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit c8277d2d8d7fffa7bb982a685ae139b0742fa807 +Subproject commit 0016ebbf2e5f7f4b1adf0b7a1d6b81afe24c08ed diff --git a/src/mina b/src/mina index a0dda4e114..0cefbab79b 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit a0dda4e1142d40c99fcecd3a5103b5501d86ddcd +Subproject commit 0cefbab79bb886eb5e99a1cb56e7ad0cda6a99ca From b6ed2b067c4d5a77b9cfa51d0e5e373277e89d4b Mon Sep 17 00:00:00 2001 From: gabrielbosio Date: Wed, 13 Mar 2024 23:46:35 -0300 Subject: [PATCH 11/17] Implement srs serialization --- src/bindings | 2 +- src/mina | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bindings b/src/bindings index 0016ebbf2e..64ec8fd607 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 0016ebbf2e5f7f4b1adf0b7a1d6b81afe24c08ed +Subproject commit 64ec8fd6076074a6b3ec2ab6735fe6f00371a1c0 diff --git a/src/mina b/src/mina index 0cefbab79b..c37e060d2a 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit 0cefbab79bb886eb5e99a1cb56e7ad0cda6a99ca +Subproject commit c37e060d2a633a2368af4ecff0ec86ceeaf430ec From 500404cd2d0650899203a770ac6e403169a0b7a4 Mon Sep 17 00:00:00 2001 From: gabrielbosio Date: Thu, 14 Mar 2024 00:19:18 -0300 Subject: [PATCH 12/17] Log verifier srs --- src/bindings | 2 +- src/mina | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bindings b/src/bindings index 64ec8fd607..5665fb872a 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 64ec8fd6076074a6b3ec2ab6735fe6f00371a1c0 +Subproject commit 5665fb872ad50739581b171523f7702e2e7526ab diff --git a/src/mina b/src/mina index c37e060d2a..5c6df73a8a 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit c37e060d2a633a2368af4ecff0ec86ceeaf430ec +Subproject commit 5c6df73a8ae18109b86dfe78ee35db9401ec29eb From af2045de74d65db2efc14f72e22f787aee6d806f Mon Sep 17 00:00:00 2001 From: gabrielbosio Date: Thu, 14 Mar 2024 00:47:40 -0300 Subject: [PATCH 13/17] Log srs depth --- src/bindings | 2 +- src/mina | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bindings b/src/bindings index 5665fb872a..4581f347db 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 5665fb872ad50739581b171523f7702e2e7526ab +Subproject commit 4581f347db082776310e4a1b67c37728d2e5251d diff --git a/src/mina b/src/mina index 5c6df73a8a..8670a1ffdc 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit 5c6df73a8ae18109b86dfe78ee35db9401ec29eb +Subproject commit 8670a1ffdc598dbe5b75cde8cb0e6dfe94561c3f From e121fa6a60b4471e9be803802a10992432cc69bd Mon Sep 17 00:00:00 2001 From: gabrielbosio Date: Thu, 14 Mar 2024 01:01:40 -0300 Subject: [PATCH 14/17] Hardcode bn254 srs depth --- src/bindings | 2 +- src/mina | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bindings b/src/bindings index 4581f347db..41336a5a17 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 4581f347db082776310e4a1b67c37728d2e5251d +Subproject commit 41336a5a17aab03ecf8636a7d6be3bc2646dda61 diff --git a/src/mina b/src/mina index 8670a1ffdc..69f66cc620 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit 8670a1ffdc598dbe5b75cde8cb0e6dfe94561c3f +Subproject commit 69f66cc620d0880bd5bf3f86a8ca2a8e807a2d1b From a8c55dbc6be5c3db9e7e9dd72d9f54598b412614 Mon Sep 17 00:00:00 2001 From: gabrielbosio Date: Thu, 14 Mar 2024 02:57:19 -0300 Subject: [PATCH 15/17] Serialize public inputs as limbs --- src/bindings | 2 +- src/mina | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bindings b/src/bindings index 41336a5a17..4a7303ffd3 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 41336a5a17aab03ecf8636a7d6be3bc2646dda61 +Subproject commit 4a7303ffd3c052c56b7f42d58768ca11f6d8cdba diff --git a/src/mina b/src/mina index 69f66cc620..14cf1e69e9 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit 69f66cc620d0880bd5bf3f86a8ca2a8e807a2d1b +Subproject commit 14cf1e69e965c6be3340445abc19fd2f85bfe78a From a4ef5eb745a5cd277c11a5d7b3c3fdd1dde1ab0f Mon Sep 17 00:00:00 2001 From: gabrielbosio Date: Thu, 14 Mar 2024 19:27:48 -0300 Subject: [PATCH 16/17] Update kimchi --- src/bindings | 2 +- src/mina | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bindings b/src/bindings index 4a7303ffd3..ac4d3a9335 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 4a7303ffd3c052c56b7f42d58768ca11f6d8cdba +Subproject commit ac4d3a93357bb751b8644a238d4e1a3e20a7c85b diff --git a/src/mina b/src/mina index 14cf1e69e9..10bd2aa90f 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit 14cf1e69e965c6be3340445abc19fd2f85bfe78a +Subproject commit 10bd2aa90f0872a7f61983301870a68946eeaaa1 From 0dea027dd62464a7548c80a83b75cdc2e0d4dce3 Mon Sep 17 00:00:00 2001 From: gabrielbosio Date: Thu, 18 Apr 2024 18:45:51 -0300 Subject: [PATCH 17/17] Expose bn254 Poseidon --- src/bindings | 2 +- src/index.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index ac4d3a9335..dee86310ac 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit ac4d3a93357bb751b8644a238d4e1a3e20a7c85b +Subproject commit dee86310ac7c1c0ac745df17df0245ec64cbd743 diff --git a/src/index.ts b/src/index.ts index 81afa8f57a..5310349eac 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,6 +10,7 @@ export { export { createForeignCurve, ForeignCurve } from './lib/foreign-curve.js'; export { createEcdsa, EcdsaSignature } from './lib/foreign-ecdsa.js'; export { Poseidon, TokenSymbol, ProvableHashable } from './lib/hash.js'; +export { Poseidon as PoseidonBn254 } from './lib/hash-bn254.js'; export { Keccak } from './lib/keccak.js'; export { Hash } from './lib/hashes-combined.js';