Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move Field to JS #12

Merged
merged 19 commits into from
May 31, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
183,124 changes: 91,764 additions & 91,360 deletions compiled/node_bindings/snarky_js_node.bc.cjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion compiled/node_bindings/snarky_js_node.bc.map

Large diffs are not rendered by default.

54 changes: 27 additions & 27 deletions compiled/web_bindings/snarky_js_web.bc.js

Large diffs are not rendered by default.

5 changes: 1 addition & 4 deletions crypto/poseidon.unit-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import { testPoseidonLegacyFp } from './test_vectors/poseidonLegacy.js';
import { expect } from 'expect';
import { bigIntToBytes } from './bigint-helpers.js';
import { test, Random } from '../../lib/testing/property.js';
import { Fp } from './finite_field.js';
import { Poseidon as SnarkyPoseidon } from '../../lib/hash.js';
import { Field } from '../../snarky.js';
import { Field } from '../../lib/core.js';

let testVectors = testPoseidonKimchiFp.test_vectors;

Expand All @@ -31,7 +30,6 @@ for (let i = 0; i < testVectors.length; i++) {
console.log('poseidon implementation matches the test vectors! 🎉');

test(Random.array(Random.field, Random.nat(20)), (xs) => {

let g1 = Poseidon.hashToGroup(xs);
let g2 = SnarkyPoseidon.hashToGroup(xs.map(Field));

Expand All @@ -42,7 +40,6 @@ test(Random.array(Random.field, Random.nat(20)), (xs) => {
expect(g1?.y.x1).toEqual(g2.y.x1.toBigInt());
});


console.log('poseidon hashToGroup implementations match! 🎉');

function fieldToHex(x: bigint) {
Expand Down
5 changes: 5 additions & 0 deletions js/snarky-class-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ export default [
name: 'Snarky',
props: [
{ name: 'exists', type: 'function' },
{ name: 'existsVar', type: 'function' },
{
name: 'asProver',
type: 'function',
Expand All @@ -213,6 +214,10 @@ export default [
name: 'constraintSystem',
type: 'function',
},
{
name: 'field',
type: 'object',
},
{
name: 'circuit',
type: 'object',
Expand Down
2 changes: 1 addition & 1 deletion lib/encoding.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { bytesToBigInt, changeBase } from '../crypto/bigint-helpers.js';
import { Field } from '../../snarky.js';
import { Field } from '../../lib/core.js';

export {
stringToFields,
Expand Down
103 changes: 98 additions & 5 deletions ocaml/lib/snarky_js_bindings_lib.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1249,11 +1249,11 @@ end
module Snarky = struct
let typ (size_in_fields : int) = Typ.array ~length:size_in_fields Field.typ

let exists (size_in_fields : int)
(compute : unit -> field_class Js.t Js.js_array Js.t) =
Impl.exists (typ size_in_fields) ~compute:(fun () ->
compute () |> Js.to_array |> Array.map ~f:of_js_field_unchecked )
|> Array.map ~f:to_js_field |> Js.array
let exists (size_in_fields : int) (compute : unit -> Field.Constant.t array) =
Impl.exists (typ size_in_fields) ~compute

let exists_var (compute : unit -> Field.Constant.t) =
Impl.exists Field.typ ~compute

let as_prover = Impl.as_prover

Expand Down Expand Up @@ -1288,6 +1288,66 @@ module Snarky = struct
Backend.R1CS_constraint_system.to_json cs |> Js.string |> json_parse
end

module Field = struct
(** add x, y to get a new AST node Add(x, y); handles if x, y are constants *)
let add x y = Field.add x y

(** scale x by a constant to get a new AST node Scale(c, x); handles if x is a constant; handles c=0,1 *)
let scale x c = Field.scale x c

(** witnesses z = x*y and constrains it with [assert_r1cs]; handles constants *)
let mul x y = Field.mul x y

(** evaluates a CVar by unfolding the AST and reading Vars from a list of public input + aux values *)
let read_var (x : Field.t) = As_prover.read_var x

(** x === y without handling of constants *)
let assert_equal x y = Impl.assert_ (Impl.Constraint.equal x y)

(** x*y === z without handling of constants *)
let assert_mul x y z = Impl.assert_ (Impl.Constraint.r1cs x y z)

(** x*x === y without handling of constants *)
let assert_square x y = Impl.assert_ (Impl.Constraint.square x y)

(** x*x === x without handling of constants *)
let assert_boolean x = Impl.assert_ (Impl.Constraint.boolean x)

(** check x < y and x <= y.
this is used in all comparisons, including with assert *)
let compare (bit_length : int) x y =
let ({ less; less_or_equal } : Field.comparison_result) =
Field.compare ~bit_length x y
in
(less, less_or_equal)

let to_bits (length : int) x =
Field.choose_preimage_var ~length x |> Array.of_list

let from_bits bits = Array.to_list bits |> Field.project

(** returns x truncated to the lowest [length] bits
=> can be used to assert that x fits in [length] bits.

more efficient than [to_bits] because it uses the [EC_endoscalar] gate;
does 16 bits per row (vs 1 bits per row that you can do with generic gates).
[length] has to be a multiple of 16
*)
let truncate_to_bits (length : int) x =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we make this parameter length_div_16 instead and multiply by 16 in the body of the function -- then the function is total and the caller can prove that it's a multiple of 16 by dividing it by 16 before passing it in.

Consider changing the name to truncate_to_bits16 also

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the input length is a plain integer, which will define the circuit statically, so the caller can't / doesn't have to prove anything about it.

do you still think we should change the signature?
I'm going to change it here but not in the user-facing SnarkyJS method for now (but will throw a quick error there)

assert (length mod 16 = 0) ;
let _a, _b, x0 =
Pickles.Scalar_challenge.to_field_checked' ~num_bits:length
(module Impl)
{ inner = x }
in
x0

(* can be implemented with Field.to_constant_and_terms *)
let seal x = Pickles.Util.seal (module Impl) x

let to_constant_and_terms x = Field.to_constant_and_terms x
end

module Circuit = struct
module Main = struct
let of_js (main : public_input_js -> unit) =
Expand Down Expand Up @@ -1334,6 +1394,8 @@ let snarky =
object%js
method exists = Snarky.exists

method existsVar = Snarky.exists_var

method asProver = Snarky.as_prover

method runAndCheck = Snarky.run_and_check
Expand All @@ -1342,6 +1404,37 @@ let snarky =

method constraintSystem = Snarky.constraint_system

val field =
object%js
method add = Snarky.Field.add

method scale = Snarky.Field.scale

method mul = Snarky.Field.mul

method readVar = Snarky.Field.read_var

method assertEqual = Snarky.Field.assert_equal

method assertMul = Snarky.Field.assert_mul

method assertSquare = Snarky.Field.assert_square

method assertBoolean = Snarky.Field.assert_boolean

method compare = Snarky.Field.compare

method toBits = Snarky.Field.to_bits

method fromBits = Snarky.Field.from_bits

method truncateToBits = Snarky.Field.truncate_to_bits

method seal = Snarky.Field.seal

method toConstantAndTerms = Snarky.Field.to_constant_and_terms
end

val circuit =
object%js
method compile = Snarky.Circuit.compile
Expand Down