Skip to content

Commit

Permalink
Merge pull request #11053 from MinaProtocol/feature/faster-zkapp-proofs
Browse files Browse the repository at this point in the history
Fast zkapp proofs with max_proofs_verified=0
  • Loading branch information
mitschabaude authored Jun 7, 2022
2 parents 7ac6b53 + 6bb6197 commit 874163a
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 49 deletions.
145 changes: 99 additions & 46 deletions src/lib/snarky_js_bindings/lib/snarky_js_bindings_lib.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) =
Expand Down Expand Up @@ -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 ())) ) ;
Expand Down Expand Up @@ -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) =
Expand All @@ -517,13 +557,18 @@ 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 ;
add_op2 "and" Boolean.( &&& ) ;
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
Expand Down Expand Up @@ -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
}
Expand All @@ -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

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 =
Expand All @@ -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:
Expand All @@ -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 ->
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 ;
Expand Down
2 changes: 1 addition & 1 deletion src/lib/snarky_js_bindings/snarky_js_types.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
2 changes: 1 addition & 1 deletion src/lib/snarky_js_bindings/snarkyjs
11 changes: 11 additions & 0 deletions src/lib/snarky_js_bindings/test_module/simple-zkapp-mock-apply.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
Mina,
signFeePayer,
Permissions,
Ledger,
} from "snarkyjs";
import { tic, toc } from "./tictoc.js";

Expand Down Expand Up @@ -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();
Expand Down
13 changes: 12 additions & 1 deletion src/lib/snarky_js_bindings/test_module/simple-zkapp.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
shutdown,
addCachedAccount,
Mina,
Ledger,
} from "snarkyjs";

await isReady;
Expand Down Expand Up @@ -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));
});
Expand All @@ -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);
}

Expand Down

0 comments on commit 874163a

Please sign in to comment.