diff --git a/src/lib/snarky_js_bindings/lib/snarky_js_bindings_lib.ml b/src/lib/snarky_js_bindings/lib/snarky_js_bindings_lib.ml index 3353ffe745d..4608189cec2 100644 --- a/src/lib/snarky_js_bindings/lib/snarky_js_bindings_lib.ml +++ b/src/lib/snarky_js_bindings/lib/snarky_js_bindings_lib.ml @@ -51,30 +51,56 @@ module As_field = struct let of_field_obj (x : field_class Js.t) : t = Obj.magic x + let of_number_exn (value : t) : Impl.Field.t = + let number : Js.number Js.t = Obj.magic value in + let float = Js.float_of_number number in + if Float.is_integer float then + if float >= 0. then + Impl.Field.( + constant @@ Constant.of_string @@ Js.to_string @@ number##toString) + else + let number : Js.number Js.t = Obj.magic (-.float) in + Impl.Field.negate + Impl.Field.( + constant @@ Constant.of_string @@ Js.to_string @@ number##toString) + else raise_error "Cannot convert a float to a field element" + + let of_boolean (value : t) : Impl.Field.t = + let value = Js.to_bool (Obj.magic value) in + if value then Impl.Field.one else Impl.Field.zero + + let of_string_exn (value : t) : Impl.Field.t = + let value : Js.js_string Js.t = Obj.magic value in + let s = Js.to_string value in + try + Impl.Field.constant + ( if + String.length s >= 2 + && Char.equal s.[0] '0' + && Char.equal (Char.lowercase_ascii s.[1]) 'x' + then Kimchi_pasta.Pasta.Fp.(of_bigint (Bigint.of_hex_string s)) + else if String.length s >= 1 && Char.equal s.[0] '-' then + String.sub s 1 (String.length s - 1) + |> Impl.Field.Constant.of_string |> Impl.Field.Constant.negate + else Impl.Field.Constant.of_string s ) + with Failure e -> raise_error e + + let of_bigint_exn (value : t) : Impl.Field.t = + let bigint : < toString : Js.js_string Js.t Js.meth > Js.t = + Obj.magic value + in + bigint##toString |> Obj.magic |> of_string_exn + let value (value : t) : Impl.Field.t = match Js.to_string (Js.typeof (Obj.magic value)) with | "number" -> - let value = Js.float_of_number (Obj.magic value) in - if Float.is_integer value then - let value = Float.to_int value in - if value >= 0 then Impl.Field.of_int value - else Impl.Field.negate (Impl.Field.of_int (-value)) - else raise_error "Cannot convert a float to a field element" + of_number_exn value | "boolean" -> - let value = Js.to_bool (Obj.magic value) in - if value then Impl.Field.one else Impl.Field.zero - | "string" -> ( - let value : Js.js_string Js.t = Obj.magic value in - let s = Js.to_string value in - try - Impl.Field.constant - ( if - String.length s >= 2 - && Char.equal s.[0] '0' - && Char.equal (Char.lowercase_ascii s.[1]) 'x' - then Kimchi_pasta.Pasta.Fp.(of_bigint (Bigint.of_hex_string s)) - else Impl.Field.Constant.of_string s ) - with Failure e -> raise_error e ) + of_boolean value + | "string" -> + of_string_exn value + | "bigint" -> + of_bigint_exn value | "object" -> let is_array = Js.to_bool (Js.Unsafe.global ##. Array##isArray value) in if is_array then @@ -228,6 +254,11 @@ let optdef_arg_method (type a) class_ (name : string) in Js.Unsafe.set prototype (Js.string name) meth +let to_js_bigint = + let bigint_constr = Js.Unsafe.eval_string {js|BigInt|js} in + fun (s : Js.js_string Js.t) -> + Js.Unsafe.fun_call bigint_constr [| Js.Unsafe.inject s |] + let to_js_field x : field_class Js.t = new%js field_constr (As_field.of_field x) let of_js_field (x : field_class Js.t) : Field.t = x##.value @@ -278,6 +309,7 @@ let () = method_ "sizeInFields" (fun _this : int -> 1) ; method_ "toFields" (fun this : field_class Js.t Js.js_array Js.t -> singleton_array this ) ; + method_ "toBigInt" (fun this -> to_string this##.value |> to_js_bigint) ; ((* TODO: Make this work with arbitrary bit length *) let bit_length = Field.size_in_bits - 2 in let cmp_method (name, f) = @@ -358,6 +390,10 @@ let () = in field_class##.one := mk Field.one ; field_class##.zero := mk Field.zero ; + field_class##.minusOne := mk @@ Field.negate Field.one ; + Js.Unsafe.set field_class (Js.string "ORDER") + ( to_js_bigint @@ Js.string @@ Pasta_bindings.BigInt256.to_string + @@ Pasta_bindings.Fp.size () ) ; field_class##.random := Js.wrap_callback (fun () : field_class Js.t -> mk (Field.constant (Field.Constant.random ())) ) ; @@ -494,7 +530,11 @@ let () = else Field.Constant.of_string s ) ) with Failure _ -> Js.Opt.empty ) | _ -> - Js.Opt.empty ) + Js.Opt.empty ) ; + let from f x = new%js field_constr (As_field.of_field (f x)) in + static_method "fromNumber" (from As_field.of_number_exn) ; + static_method "fromString" (from As_field.of_string_exn) ; + static_method "fromBigInt" (from As_field.of_bigint_exn) let () = let handle_constants2 f f_constant (x : Boolean.var) (y : Boolean.var) = @@ -517,6 +557,8 @@ let () = method_ name (fun this (y : As_bool.t) : bool_class Js.t -> mk (f this##.value (As_bool.value y)) ) in + Js.Unsafe.set bool_class (Js.string "true") (mk Boolean.true_) ; + Js.Unsafe.set bool_class (Js.string "false") (mk Boolean.false_) ; method_ "toField" (fun this : field_class Js.t -> new%js field_constr (As_field.of_field (this##.value :> Field.t)) ) ; add_op1 "not" Boolean.not ; @@ -524,6 +566,9 @@ let () = add_op2 "or" Boolean.( ||| ) ; method_ "assertEquals" (fun this (y : As_bool.t) : unit -> Boolean.Assert.( = ) this##.value (As_bool.value y) ) ; + method_ "assertTrue" (fun this : unit -> Boolean.Assert.is_true this##.value) ; + method_ "assertFalse" (fun this : unit -> + Boolean.Assert.( = ) this##.value Boolean.false_ ) ; add_op2 "equals" equal ; method_ "toBoolean" (fun this : bool Js.t -> match (this##.value :> Field.t) with @@ -1570,7 +1615,7 @@ module Zkapp_statement = struct val atParty = to_js_field at_party end - let of_js (statement : zkapp_statement_js) = + let of_js (statement : zkapp_statement_js) : t = { transaction = of_js_field statement##.transaction ; at_party = of_js_field statement##.atParty } @@ -1585,6 +1630,11 @@ module Zkapp_statement = struct { transaction = Field.constant transaction ; at_party = Field.constant at_party } + + let of_js (statement : zkapp_statement_js) : t = + { transaction = of_js_field statement##.transaction |> to_unchecked + ; at_party = of_js_field statement##.atParty |> to_unchecked + } end end @@ -1642,26 +1692,11 @@ let create_pickles_rule ((identifier, main) : pickles_rule_js) = ; main_value = (fun _ _ -> []) } -let dummy_rule self = - { identifier = "dummy" - ; prevs = [ self; self ] - ; main_value = (fun _ _ -> [ true; true ]) - ; main = - (fun _ _ -> - dummy_constraints () ; - (* unsatisfiable *) - let x = - Impl.exists Field.typ ~compute:(fun () -> Field.Constant.zero) - in - Field.(Assert.equal x (x + one)) ; - Boolean.[ true_; true_ ] ) - } - let other_verification_key_constr : (Other_impl.Verification_key.t -> verification_key_class Js.t) Js.constr = Obj.magic verification_key_class -type proof = (Pickles_types.Nat.N2.n, Pickles_types.Nat.N2.n) Pickles.Proof.t +type proof = (Pickles_types.Nat.N0.n, Pickles_types.Nat.N0.n) Pickles.Proof.t module Statement_with_proof = Pickles_types.Hlist.H3.T (Pickles.Statement_with_proof) @@ -1696,10 +1731,8 @@ let nat_module (i : int) : (module Pickles_types.Nat.Intf) = let pickles_compile (choices : pickles_rule_js Js.js_array Js.t) = let choices = choices |> Js.to_array |> Array.to_list in - let branches = List.length choices + 1 in - let choices ~self = - List.map choices ~f:create_pickles_rule @ [ dummy_rule self ] - in + let branches = List.length choices in + let choices ~self:_ = List.map choices ~f:create_pickles_rule in let (module Branches) = nat_module branches in (* TODO get rid of Obj.magic for choices *) let tag, _cache, p, provers = @@ -1708,7 +1741,7 @@ let pickles_compile (choices : pickles_rule_js Js.js_array Js.t) = (module Zkapp_statement.Constant) ~typ:zkapp_statement_typ ~branches:(module Branches) - ~max_proofs_verified:(module Pickles_types.Nat.N2) + ~max_proofs_verified:(module Pickles_types.Nat.N0) (* ^ TODO make max_branching configurable -- needs refactor in party types *) ~name:"smart-contract" ~constraint_constants: @@ -1731,15 +1764,18 @@ let pickles_compile (choices : pickles_rule_js Js.js_array Js.t) = (* TODO: get rid of Obj.magic, this should be an empty "H3.T" *) let prevs = Obj.magic [] in let statement = Zkapp_statement.(statement_js |> of_js |> to_constant) in - prover ?handler:None prevs statement |> Promise_js_helpers.to_js + prover ?handler:None prevs statement + |> Promise.map ~f:Pickles.Side_loaded.Proof.of_proof + |> Promise_js_helpers.to_js in prove in let rec to_js_provers : type a b c. (a, b, c, Zkapp_statement.Constant.t, proof Promise.t) Pickles.Provers.t - -> (zkapp_statement_js -> proof Promise_js_helpers.js_promise) list = - function + -> ( zkapp_statement_js + -> Pickles.Side_loaded.Proof.t Promise_js_helpers.js_promise ) + list = function | [] -> [] | p :: ps -> @@ -2198,6 +2234,21 @@ module Ledger = struct | Proof _ | None_given -> () ) + let verify_party_proof (statement : zkapp_statement_js) + (proof : Js.js_string Js.t) (vk : Js.js_string Js.t) = + let statement = Zkapp_statement.Constant.of_js statement in + let proof = + Result.ok_or_failwith + (Pickles.Side_loaded.Proof.of_base64 (Js.to_string proof)) + in + let vk = + Pickles.Side_loaded.Verification_key.of_base58_check_exn (Js.to_string vk) + in + Pickles.Side_loaded.verify_promise + [ (vk, statement, proof) ] + ~value_to_field_elements:Zkapp_statement.Constant.to_field_elements + |> Promise.map ~f:Js.bool |> Promise_js_helpers.to_js + let public_key_to_string (pk : public_key) : Js.js_string Js.t = pk |> public_key |> Signature_lib.Public_key.Compressed.to_base58_check |> Js.string @@ -2336,6 +2387,8 @@ module Ledger = struct static_method "signFieldElement" sign_field_element ; static_method "signFeePayer" sign_fee_payer ; static_method "signOtherParty" sign_other_party ; + static_method "verifyPartyProof" verify_party_proof ; + static_method "publicKeyToString" public_key_to_string ; static_method "publicKeyOfString" public_key_of_string ; static_method "privateKeyToString" private_key_to_string ; diff --git a/src/lib/snarky_js_bindings/snarky_js_types.ml b/src/lib/snarky_js_bindings/snarky_js_types.ml index fa72f769756..43ad5a8e2b3 100644 --- a/src/lib/snarky_js_bindings/snarky_js_types.ml +++ b/src/lib/snarky_js_bindings/snarky_js_types.ml @@ -5,8 +5,8 @@ let () = let js_layout = `Assoc [ ("Parties", layout Parties.deriver) - ; ("BalanceChange", layout Fields_derivers_zkapps.Derivers.balance_change) ; ("Party", layout Party.Graphql_repr.deriver) ] in + print_endline (js_layout |> Yojson.Safe.pretty_to_string) diff --git a/src/lib/snarky_js_bindings/snarkyjs b/src/lib/snarky_js_bindings/snarkyjs index ff13a1c79e2..902462c594d 160000 --- a/src/lib/snarky_js_bindings/snarkyjs +++ b/src/lib/snarky_js_bindings/snarkyjs @@ -1 +1 @@ -Subproject commit ff13a1c79e2459242d5cfd0e9b4f75327453de76 +Subproject commit 902462c594df23423e938299036f987afd5dfebd diff --git a/src/lib/snarky_js_bindings/test_module/simple-zkapp-mock-apply.js b/src/lib/snarky_js_bindings/test_module/simple-zkapp-mock-apply.js index 8a062bca5f6..845a8ab19ee 100644 --- a/src/lib/snarky_js_bindings/test_module/simple-zkapp-mock-apply.js +++ b/src/lib/snarky_js_bindings/test_module/simple-zkapp-mock-apply.js @@ -11,6 +11,7 @@ import { Mina, signFeePayer, Permissions, + Ledger, } from "snarkyjs"; import { tic, toc } from "./tictoc.js"; @@ -90,6 +91,16 @@ partiesJsonInitialize = signFeePayer(partiesJsonInitialize, senderKey, { }); toc(); +// verify the proof +tic("verify transaction proof"); +let parties = JSON.parse(partiesJsonInitialize); +let proof = parties.otherParties[0].authorization.proof; +let statement = Ledger.transactionStatement(partiesJsonInitialize, 0); +let ok = await Ledger.verifyPartyProof(statement, proof, verificationKey.data); +toc(); +console.log("did proof verify?", ok); +if (!ok) throw Error("proof didn't verify"); + tic("apply initialize transaction"); Local.applyJsonTransaction(partiesJsonInitialize); toc(); diff --git a/src/lib/snarky_js_bindings/test_module/simple-zkapp.js b/src/lib/snarky_js_bindings/test_module/simple-zkapp.js index 88bd4363cd1..3147d5df3de 100644 --- a/src/lib/snarky_js_bindings/test_module/simple-zkapp.js +++ b/src/lib/snarky_js_bindings/test_module/simple-zkapp.js @@ -11,6 +11,7 @@ import { shutdown, addCachedAccount, Mina, + Ledger, } from "snarkyjs"; await isReady; @@ -91,7 +92,7 @@ if (command === "update") { publicKey: zkappAddress, zkapp: { appState: [initialState, 0, 0, 0, 0, 0, 0, 0] }, }); - await SimpleZkapp.compile(zkappAddress); + let { verificationKey } = await SimpleZkapp.compile(zkappAddress); let transaction = await Mina.transaction(() => { new SimpleZkapp(zkappAddress).update(Field(2)); }); @@ -110,6 +111,16 @@ if (command === "update") { { parties, feePayer }, feePayerKeyBase58 ); + parties = JSON.parse(data.parties); + let proof = parties.otherParties[0].authorization.proof; + let statement = Ledger.transactionStatement(data.parties, 0); + let ok = await Ledger.verifyPartyProof( + statement, + proof, + verificationKey.data + ); + if (!ok) throw Error("verification failed"); + console.log(data.parties); }