diff --git a/src/app/cli/src/init/transaction_snark_profiler.ml b/src/app/cli/src/init/transaction_snark_profiler.ml index a6ba126bd7d..cae3409c7a7 100644 --- a/src/app/cli/src/init/transaction_snark_profiler.ml +++ b/src/app/cli/src/init/transaction_snark_profiler.ml @@ -609,8 +609,11 @@ let profile_zkapps ~verifier ledger zkapp_commands = let v_start_time = Time.now () in let%bind res = Verifier.verify_commands verifier - [ User_command.to_verifiable ~ledger ~get:Mina_ledger.Ledger.get - ~location_of_account:Mina_ledger.Ledger.location_of_account + [ User_command.to_verifiable + ~find_vk: + (Zkapp_command.Verifiable.find_vk_via_ledger ~ledger + ~get:Mina_ledger.Ledger.get + ~location_of_account:Mina_ledger.Ledger.location_of_account ) (Zkapp_command zkapp_command) |> Or_error.ok_exn ] diff --git a/src/lib/mina_base/mina_base.ml b/src/lib/mina_base/mina_base.ml index bbd9c6cdbdd..d03ffa60f9b 100644 --- a/src/lib/mina_base/mina_base.ml +++ b/src/lib/mina_base/mina_base.ml @@ -60,5 +60,6 @@ module Transaction_status = Transaction_status module Transaction_union_payload = Transaction_union_payload module Transaction_union_tag = Transaction_union_tag module User_command = User_command +module Verification_key_wire = Verification_key_wire module With_stack_hash = With_stack_hash module With_status = With_status diff --git a/src/lib/mina_base/transaction_status.ml b/src/lib/mina_base/transaction_status.ml index 8108590bc28..f402335dc74 100644 --- a/src/lib/mina_base/transaction_status.ml +++ b/src/lib/mina_base/transaction_status.ml @@ -46,6 +46,7 @@ module Failure = struct | Account_proved_state_precondition_unsatisfied | Account_is_new_precondition_unsatisfied | Protocol_state_precondition_unsatisfied + | Unexpected_verification_key_hash | Incorrect_nonce | Invalid_fee_excess | Cancelled @@ -127,7 +128,8 @@ module Failure = struct List.init 8 ~f:var.constructor @ acc ) ~account_proved_state_precondition_unsatisfied:add ~account_is_new_precondition_unsatisfied:add - ~protocol_state_precondition_unsatisfied:add ~incorrect_nonce:add + ~protocol_state_precondition_unsatisfied:add + ~unexpected_verification_key_hash:add ~incorrect_nonce:add ~invalid_fee_excess:add ~cancelled:add let gen = Quickcheck.Generator.of_list all @@ -211,6 +213,8 @@ module Failure = struct "Account_is_new_precondition_unsatisfied" | Protocol_state_precondition_unsatisfied -> "Protocol_state_precondition_unsatisfied" + | Unexpected_verification_key_hash -> + "Unexpected_verification_key_hash" | Incorrect_nonce -> "Incorrect_nonce" | Invalid_fee_excess -> @@ -295,6 +299,8 @@ module Failure = struct Ok Account_is_new_precondition_unsatisfied | "Protocol_state_precondition_unsatisfied" -> Ok Protocol_state_precondition_unsatisfied + | "Unexpected_verification_key_hash" -> + Ok Unexpected_verification_key_hash | "Incorrect_nonce" -> Ok Incorrect_nonce | "Invalid_fee_excess" -> @@ -436,6 +442,9 @@ module Failure = struct "The account update's account is-new state precondition was unsatisfied" | Protocol_state_precondition_unsatisfied -> "The account update's protocol state precondition unsatisfied" + | Unexpected_verification_key_hash -> + "The account update's verification key hash does not match the \ + verification key in the ledger account" | Incorrect_nonce -> "Incorrect nonce" | Invalid_fee_excess -> diff --git a/src/lib/mina_base/user_command.ml b/src/lib/mina_base/user_command.ml index 2290cd902ab..df51b4961db 100644 --- a/src/lib/mina_base/user_command.ml +++ b/src/lib/mina_base/user_command.ml @@ -159,15 +159,61 @@ module Verifiable = struct Account_update.Fee_payer.account_id p.fee_payer end -let to_verifiable (t : t) ~ledger ~get ~location_of_account : - Verifiable.t Or_error.t = +let to_verifiable (t : t) ~find_vk : Verifiable.t Or_error.t = match t with | Signed_command c -> Ok (Signed_command c) | Zkapp_command cmd -> - Zkapp_command.Verifiable.create ~ledger ~get ~location_of_account cmd + Zkapp_command.Verifiable.create ~find_vk cmd |> Or_error.map ~f:(fun cmd -> Zkapp_command cmd) +module Make_to_all_verifiable (Strategy : sig + val create_all : + Zkapp_command.t list + -> find_vk: + ( Zkapp_basic.F.t + -> Account_id.t + -> (Verification_key_wire.t, Error.t) Result.t ) + -> Zkapp_command.Verifiable.t list Or_error.t +end) = +struct + let to_all_verifiable (ts : t list) ~find_vk : Verifiable.t list Or_error.t = + let open Or_error.Let_syntax in + (* First we tag everything with its index *) + let its = List.mapi ts ~f:(fun i x -> (i, x)) in + (* then we partition out the zkapp commands *) + let izk_cmds, is_cmds = + List.partition_map its ~f:(fun (i, cmd) -> + match cmd with + | Zkapp_command c -> + First (i, c) + | Signed_command c -> + Second (i, c) ) + in + (* then unzip the indices *) + let ixs, zk_cmds = List.unzip izk_cmds in + (* then we verify the zkapp commands *) + let%map vzk_cmds = Strategy.create_all ~find_vk zk_cmds in + (* rezip indices *) + let ivzk_cmds = List.zip_exn ixs vzk_cmds in + (* Put them back in with a sort by index (un-partition) *) + let ivs = + List.map is_cmds ~f:(fun (i, cmd) -> (i, Signed_command cmd)) + @ List.map ivzk_cmds ~f:(fun (i, cmd) -> (i, Zkapp_command cmd)) + |> List.sort ~compare:(fun (i, _) (j, _) -> i - j) + in + (* Drop the indices *) + List.unzip ivs |> snd +end + +module Any = struct + include Make_to_all_verifiable (Zkapp_command.Verifiable.Any) +end + +module Last = struct + include Make_to_all_verifiable (Zkapp_command.Verifiable.Last) +end + let of_verifiable (t : Verifiable.t) : t = match t with | Signed_command x -> @@ -212,6 +258,12 @@ let applicable_at_nonce (t : t) = let expected_target_nonce t = Account.Nonce.succ (applicable_at_nonce t) +let extract_vks : t -> Verification_key_wire.t List.t = function + | Signed_command _ -> + [] + | Zkapp_command cmd -> + Zkapp_command.extract_vks cmd + (** The target nonce is what the nonce of the fee payer will be after a user command is successfully applied. *) let target_nonce_on_success (t : t) = match t with @@ -239,6 +291,8 @@ let valid_until (t : t) = Mina_numbers.Global_slot.max_value ) module Valid = struct + type t_ = t + [%%versioned module Stable = struct module V2 = struct @@ -255,7 +309,7 @@ module Valid = struct module Gen = Gen_make (Signed_command.With_valid_signature) end -let check ~ledger ~get ~location_of_account (t : t) : Valid.t Or_error.t = +let check_verifiable (t : Verifiable.t) : Valid.t Or_error.t = match t with | Signed_command x -> ( match Signed_command.check x with @@ -264,9 +318,10 @@ let check ~ledger ~get ~location_of_account (t : t) : Valid.t Or_error.t = | None -> Or_error.error_string "Invalid signature" ) | Zkapp_command p -> - Or_error.map - (Zkapp_command.Valid.to_valid ~ledger ~get ~location_of_account p) - ~f:(fun p -> Zkapp_command p) + Ok (Zkapp_command (Zkapp_command.Valid.of_verifiable p)) + +let check ~find_vk (t : t) : Valid.t Or_error.t = + to_verifiable ~find_vk t |> Or_error.bind ~f:check_verifiable let forget_check (t : Valid.t) : t = match t with diff --git a/src/lib/mina_base/zkapp_basic.ml b/src/lib/mina_base/zkapp_basic.ml index ede3aeb8ab1..1b14b041006 100644 --- a/src/lib/mina_base/zkapp_basic.ml +++ b/src/lib/mina_base/zkapp_basic.ml @@ -393,6 +393,11 @@ module F = Snark_params.Tick.Field [%%endif] +module F_map = struct + include Hashable.Make (F) + include Comparable.Make (F) +end + let invalid_public_key : Public_key.Compressed.t = { x = F.zero; is_odd = false } diff --git a/src/lib/mina_base/zkapp_command.ml b/src/lib/mina_base/zkapp_command.ml index ae42e5c257d..a30200dcadd 100644 --- a/src/lib/mina_base/zkapp_command.ml +++ b/src/lib/mina_base/zkapp_command.ml @@ -1227,6 +1227,15 @@ let fee_token (_t : t) = Token_id.default let fee_payer (t : t) = Account_id.create t.fee_payer.body.public_key (fee_token t) +let extract_vks (t : t) : Verification_key_wire.t List.t = + account_updates t + |> Call_forest.fold ~init:[] ~f:(fun acc (p : Account_update.t) -> + match Account_update.verification_key_update_to_option p with + | Zkapp_basic.Set_or_keep.Set (Some vk) -> + vk :: acc + | _ -> + acc ) + let account_updates_list (t : t) : Account_update.t list = Call_forest.fold t.account_updates ~init:[] ~f:(Fn.flip List.cons) |> List.rev @@ -1360,12 +1369,46 @@ module Verifiable : sig end end] - val create : - T.t - -> ledger:'a + val find_vk_via_ledger : + ledger:'a -> get:('a -> 'b -> Account.t option) -> location_of_account:('a -> Account_id.t -> 'b option) + -> Zkapp_basic.F.t + -> Account_id.t + -> (Verification_key_wire.t, Error.t) Result.t + + val create : + T.t + -> find_vk: + ( Zkapp_basic.F.t + -> Account_id.t + -> (Verification_key_wire.t, Error.t) Result.t ) -> t Or_error.t + + module Any : sig + (** creates verifiables from a list of commands that caches verification + keys and permits _any_ vks that have been seen earlier in the list. *) + val create_all : + T.t list + -> find_vk: + ( Zkapp_basic.F.t + -> Account_id.t + -> (Verification_key_wire.t, Error.t) Result.t ) + -> t list Or_error.t + end + + module Last : sig + (** creates verifiables from a list of commands that caches verification + keys and permits only the _last_ vk that has been seen earlier in the + list. *) + val create_all : + T.t list + -> find_vk: + ( Zkapp_basic.F.t + -> Account_id.t + -> (Verification_key_wire.t, Error.t) Result.t ) + -> t list Or_error.t + end end = struct [%%versioned module Stable = struct @@ -1386,6 +1429,32 @@ end = struct end end] + let ok_if_vk_hash_expected ~got ~expected = + if not @@ Zkapp_basic.F.equal (With_hash.hash got) expected then + Error + (Error.create "Expected vk hash doesn't match hash in vk we received" + [ ("expected_vk_hash", expected) + ; ("got_vk_hash", With_hash.hash got) + ] + [%sexp_of: (string * Zkapp_basic.F.t) list] ) + else Ok got + + let find_vk_via_ledger ~ledger ~get ~location_of_account expected_vk_hash + account_id = + match + let open Option.Let_syntax in + let%bind location = location_of_account ledger account_id in + let%bind (account : Account.t) = get ledger location in + let%bind zkapp = account.zkapp in + zkapp.verification_key + with + | Some vk -> + ok_if_vk_hash_expected ~got:vk ~expected:expected_vk_hash + | None -> + Error + (Error.create "No verification key found for proved account update" + ("account_id", account_id) [%sexp_of: string * Account_id.t] ) + (* Ensures that there's a verification_key available for all account_updates * and creates a valid command associating the correct keys with each * account_id. @@ -1394,27 +1463,9 @@ end = struct * subsequent account_updates use the replaced key instead of looking in the * ledger for the key (ie set by a previous transaction). *) - let create ({ fee_payer; account_updates; memo } : T.t) ~ledger ~get - ~location_of_account : t Or_error.t = + let create ({ fee_payer; account_updates; memo } : T.t) ~find_vk : + t Or_error.t = With_return.with_return (fun { return } -> - let find_vk account_id = - match - let open Option.Let_syntax in - let%bind location = location_of_account ledger account_id in - let%bind (account : Account.t) = get ledger location in - let%bind zkapp = account.zkapp in - zkapp.verification_key - with - | Some vk -> - vk - | None -> - let err = - Error.create - "No verification key found for proved account update" - ("account_id", account_id) [%sexp_of: string * Account_id.t] - in - return (Error err) - in let tbl = Account_id.Table.create () in let vks_overridden = (* Keep track of the verification keys that have been set so far @@ -1440,40 +1491,123 @@ end = struct | Error _ as err -> return err in - if Control.(Tag.equal Tag.Proof (Control.tag p.authorization)) - then ( - let prioritized_vk = - (* only lookup _past_ vk setting, ie exclude the new one we - * potentially set in this account_update (use the non-' - * vks_overrided) . *) - match Account_id.Map.find !vks_overridden account_id with - | Some (Some vk) -> - vk - | Some None -> - (* we explicitly have erased the key *) - let err = - Error.create - "No verification key found for proved account \ - update: the verification key was removed by a \ - previous account update" - ("account_id", account_id) - [%sexp_of: string * Account_id.t] - in - return (Error err) - | None -> - (* we haven't set anything; lookup the vk in the ledger *) - find_vk account_id - in - Account_id.Table.update tbl account_id ~f:(fun _ -> - With_hash.hash prioritized_vk ) ; - (* return the updated overrides *) - vks_overridden := vks_overriden' ; - (p, Some prioritized_vk) ) - else ( - vks_overridden := vks_overriden' ; - (p, None) ) ) + match p.body.authorization_kind with + | Proof vk_hash -> + let prioritized_vk = + (* only lookup _past_ vk setting, ie exclude the new one we + * potentially set in this account_update (use the non-' + * vks_overrided) . *) + match Account_id.Map.find !vks_overridden account_id with + | Some (Some vk) -> ( + match + ok_if_vk_hash_expected ~got:vk ~expected:vk_hash + with + | Ok vk -> + vk + | Error err -> + return (Error err) ) + | Some None -> + (* we explicitly have erased the key *) + let err = + Error.create + "No verification key found for proved account \ + update: the verification key was removed by a \ + previous account update" + ("account_id", account_id) + [%sexp_of: string * Account_id.t] + in + return (Error err) + | None -> ( + (* we haven't set anything; lookup the vk in the fallback *) + match find_vk vk_hash account_id with + | Error e -> + return (Error e) + | Ok vk -> + vk ) + in + Account_id.Table.update tbl account_id ~f:(fun _ -> + With_hash.hash prioritized_vk ) ; + (* return the updated overrides *) + vks_overridden := vks_overriden' ; + (p, Some prioritized_vk) + | _ -> + vks_overridden := vks_overriden' ; + (p, None) ) in Ok { fee_payer; account_updates; memo } ) + + module Map_cache = struct + type 'a t = 'a Zkapp_basic.F_map.Map.t + + let empty = Zkapp_basic.F_map.Map.empty + + let find = Zkapp_basic.F_map.Map.find + + let set = Zkapp_basic.F_map.Map.set + end + + module Singleton_cache = struct + type 'a t = (Zkapp_basic.F.t * 'a) option + + let empty = None + + let find t key = + match t with + | None -> + None + | Some (k, v) -> + if Zkapp_basic.F.equal key k then Some v else None + + let set _ ~key ~data = Some (key, data) + end + + module Make_create_all (Cache : sig + type 'a t + + val empty : 'a t + + val find : 'a t -> Zkapp_basic.F.t -> 'a option + + val set : 'a t -> key:Zkapp_basic.F.t -> data:'a -> 'a t + end) = + struct + let create_all (cmds : T.t list) + ~(find_vk : + Zkapp_basic.F.t + -> Account_id.t + -> (Verification_key_wire.t, Error.t) Result.t ) : t list Or_error.t + = + Or_error.try_with (fun () -> + snd (* remove the helper cache we folded with *) + (List.fold_map cmds ~init:Cache.empty + ~f:(fun (running_cache : Verification_key_wire.t Cache.t) cmd -> + let verified_cmd : t = + create cmd ~find_vk:(fun vk_hash account_id -> + (* first we check if there's anything in the running + cache within this chunk so far *) + match Cache.find running_cache vk_hash with + | None -> + (* before falling back to the find_vk *) + find_vk vk_hash account_id + | Some vk -> + Ok vk ) + |> Or_error.ok_exn + in + let running_cache' = + List.fold (extract_vks cmd) ~init:running_cache + ~f:(fun acc vk -> + Cache.set acc ~key:(With_hash.hash vk) ~data:vk ) + in + (running_cache', verified_cmd) ) ) ) + end + + module Any = struct + include Make_create_all (Map_cache) + end + + module Last = struct + include Make_create_all (Singleton_cache) + end end let of_verifiable (t : Verifiable.t) : t = @@ -1544,24 +1678,10 @@ let weight (zkapp_command : t) : int = ] module type Valid_intf = sig - module Verification_key_hash : sig - [%%versioned: - module Stable : sig - module V1 : sig - type t = Zkapp_basic.F.Stable.V1.t - [@@deriving sexp, compare, equal, hash, yojson] - end - end] - end - [%%versioned: module Stable : sig module V1 : sig - type t = private - { zkapp_command : T.Stable.V1.t - ; verification_keys : - (Account_id.Stable.V2.t * Verification_key_hash.Stable.V1.t) list - } + type t = private { zkapp_command : T.Stable.V1.t } [@@deriving sexp, compare, equal, hash, yojson] end end] @@ -1571,20 +1691,15 @@ module type Valid_intf = sig val to_valid : T.t - -> ledger:'a - -> get:('a -> 'b -> Account.t option) - -> location_of_account:('a -> Account_id.t -> 'b option) + -> find_vk: + ( Zkapp_basic.F.t + -> Account_id.t + -> (Verification_key_wire.t, Error.t) Result.t ) -> t Or_error.t - val of_verifiable : Verifiable.t -> t Or_error.t + val of_verifiable : Verifiable.t -> t val forget : t -> T.t - - module For_tests : sig - val verification_keys : t -> (Account_id.t * Verification_key_hash.t) list - - val replace_zkapp_command : t -> T.t -> t - end end module Valid : @@ -1609,61 +1724,25 @@ struct module Stable = struct module V1 = struct type t = Mina_wire_types.Mina_base.Zkapp_command.Valid.V1.t = - { zkapp_command : S.V1.t - ; verification_keys : - (Account_id.Stable.V2.t * Verification_key_hash.Stable.V1.t) list - } + { zkapp_command : S.V1.t } [@@deriving sexp, compare, equal, hash, yojson] let to_latest = Fn.id end end] - let create ~verification_keys zkapp_command : t = - { zkapp_command; verification_keys } - - let of_verifiable (t : Verifiable.t) : t Or_error.t = - let open Or_error.Let_syntax in - let tbl = Account_id.Table.create () in - let%map () = - Call_forest.fold t.account_updates ~init:(Ok ()) - ~f:(fun acc (p, vk_opt) -> - let%bind _ok = acc in - let account_id = Account_update.account_id p in - let%bind () = check_authorization p in - if Control.(Tag.equal Tag.Proof (Control.tag p.authorization)) then - let%map { With_hash.hash; _ } = - match vk_opt with - | Some vk -> - Ok vk - | None -> - Or_error.errorf - "Verification key required for proof, but was not given" - in - Account_id.Table.update tbl account_id ~f:(fun _ -> hash) - else acc ) - in - { zkapp_command = of_verifiable t - ; verification_keys = Account_id.Table.to_alist tbl - } + let create zkapp_command : t = { zkapp_command } + + let of_verifiable (t : Verifiable.t) : t = { zkapp_command = of_verifiable t } let to_valid_unsafe (t : T.t) : [> `If_this_is_used_it_should_have_a_comment_justifying_it of t ] = - `If_this_is_used_it_should_have_a_comment_justifying_it - (create t ~verification_keys:[]) + `If_this_is_used_it_should_have_a_comment_justifying_it (create t) let forget (t : t) : T.t = t.zkapp_command - let to_valid (t : T.t) ~ledger ~get ~location_of_account : t Or_error.t = - Verifiable.create t ~ledger ~get ~location_of_account - |> Or_error.bind ~f:of_verifiable - - module For_tests = struct - let replace_zkapp_command (t : t) (zkapp_command : T.t) = - { t with zkapp_command } - - let verification_keys (t : t) = t.verification_keys - end + let to_valid (t : T.t) ~find_vk : t Or_error.t = + Verifiable.create t ~find_vk |> Or_error.map ~f:of_verifiable end [%%define_locally Stable.Latest.(of_yojson, to_yojson)] @@ -2165,6 +2244,36 @@ let inner_query = (Option.value_exn ~message:"Invariant: All projectable derivers are Some" Fields_derivers_zkapps.(inner_query (deriver @@ Derivers.o ())) ) +module For_tests = struct + let replace_vks t vk = + { t with + account_updates = + Call_forest.map t.account_updates ~f:(fun (p : Account_update.t) -> + { p with + body = + { p.body with + update = + { p.body.update with + verification_key = + (* replace dummy vks in vk Setting *) + ( match p.body.update.verification_key with + | Set _vk -> + Set vk + | Keep -> + Keep ) + } + ; authorization_kind = + (* replace dummy vk hashes in authorization kind *) + ( match p.body.authorization_kind with + | Proof _vk_hash -> + Proof (With_hash.hash vk) + | ak -> + ak ) + } + } ) + } +end + let%test_module "Test" = ( module struct module Fd = Fields_derivers_zkapps.Derivers diff --git a/src/lib/mina_generators/user_command_generators.ml b/src/lib/mina_generators/user_command_generators.ml index be889231b61..13f8ca9b3a0 100644 --- a/src/lib/mina_generators/user_command_generators.ml +++ b/src/lib/mina_generators/user_command_generators.ml @@ -136,8 +136,11 @@ let zkapp_command_with_ledger ?num_keypairs ?max_account_updates in let zkapp_command = Or_error.ok_exn - (Zkapp_command.Valid.to_valid ~ledger ~get:Ledger.get - ~location_of_account:Ledger.location_of_account zkapp_command ) + (Zkapp_command.Valid.to_valid + ~find_vk: + (Zkapp_command.Verifiable.find_vk_via_ledger ~ledger ~get:Ledger.get + ~location_of_account:Ledger.location_of_account ) + zkapp_command ) in (* include generated ledger in result *) return @@ -182,8 +185,12 @@ let sequence_zkapp_command_with_ledger ?max_account_updates ?max_token_updates in let valid_zkapp_command = Or_error.ok_exn - (Zkapp_command.Valid.to_valid ~ledger ~get:Ledger.get - ~location_of_account:Ledger.location_of_account zkapp_command ) + (Zkapp_command.Valid.to_valid + ~find_vk: + (Zkapp_command.Verifiable.find_vk_via_ledger ~ledger + ~get:Ledger.get + ~location_of_account:Ledger.location_of_account ) + zkapp_command ) in let zkapp_command_and_fee_payer_keypairs' = ( User_command.Zkapp_command valid_zkapp_command diff --git a/src/lib/mina_ledger/dune b/src/lib/mina_ledger/dune index bb462160217..f3a1c3ad787 100644 --- a/src/lib/mina_ledger/dune +++ b/src/lib/mina_ledger/dune @@ -51,5 +51,6 @@ quickcheck_lib snarky.backendless unsigned_extended + with_hash ppx_version.runtime )) diff --git a/src/lib/mina_ledger/ledger.ml b/src/lib/mina_ledger/ledger.ml index ecde0362966..2e48a813899 100644 --- a/src/lib/mina_ledger/ledger.ml +++ b/src/lib/mina_ledger/ledger.ml @@ -479,7 +479,7 @@ let%test_unit "tokens test" = in let main (ledger : t) = let execute_zkapp_command_transaction - (zkapp_command : + (account_updates : (Account_update.Body.Simple.t, unit, unit) Zkapp_command.Call_forest.t ) : unit = let _, ({ nonce; _ } : Account.t), _ = @@ -487,11 +487,13 @@ let%test_unit "tokens test" = (Account_id.create pk Token_id.default) |> Or_error.ok_exn in + let zkapp_command = + mk_zkapp_command ~fee:7 ~fee_payer_pk:pk ~fee_payer_nonce:nonce + account_updates + in match apply_zkapp_command_unchecked ~constraint_constants ~state_view:view - ledger - (mk_zkapp_command ~fee:7 ~fee_payer_pk:pk ~fee_payer_nonce:nonce - zkapp_command ) + ledger zkapp_command with | Ok ({ command = { status; _ }; _ }, _) -> ( match status with @@ -515,9 +517,37 @@ let%test_unit "tokens test" = () in let token_funder, _ = keypair_and_amounts.(1) in + let token_funder_pk = token_funder.public_key |> Public_key.compress in let token_owner = Keypair.create () in + let token_owner_pk = token_owner.public_key |> Public_key.compress in let token_account1 = Keypair.create () in let token_account2 = Keypair.create () in + (* patch ledger so that token funder account has Proof send permission and a + zkapp acount dummy verification key + + allows use of Proof authorization in `create_token` zkApp, below + *) + iteri ledger ~f:(fun _n acct -> + if Public_key.Compressed.equal acct.public_key token_funder_pk then + let acct_id = Account_id.create token_funder_pk Token_id.default in + let loc = Option.value_exn @@ location_of_account ledger acct_id in + let acct_with_zkapp = + { acct with + permissions = + { acct.permissions with send = Permissions.Auth_required.Proof } + ; zkapp = + Some + { Zkapp_account.default with + verification_key = + Some + With_hash. + { data = Side_loaded_verification_key.dummy + ; hash = Zkapp_account.dummy_vk_hash () + } + } + } + in + set ledger loc acct_with_zkapp ) ; let account_creation_fee = Currency.Fee.to_nanomina_int constraint_constants.account_creation_fee in @@ -525,22 +555,20 @@ let%test_unit "tokens test" = (Account_update.Body.Simple.t, unit, unit) Zkapp_command.Call_forest.t = mk_forest [ mk_node - (mk_account_update_body Signature Call token_funder Token_id.default + (mk_account_update_body + (Proof (Zkapp_account.dummy_vk_hash ())) + Call token_funder Token_id.default (-(4 * account_creation_fee)) ) [] ; mk_node - (mk_account_update_body - (Proof (Zkapp_account.dummy_vk_hash ())) - Call token_owner Token_id.default (3 * account_creation_fee) ) + (mk_account_update_body Signature Call token_owner Token_id.default + (3 * account_creation_fee) ) [] ] in let custom_token_id = Account_id.derive_token_id - ~owner: - (Account_id.create - (Public_key.compress token_owner.public_key) - Token_id.default ) + ~owner:(Account_id.create token_owner_pk Token_id.default) in let token_minting = mk_forest @@ -596,10 +624,7 @@ let%test_unit "tokens test" = in execute_zkapp_command_transaction create_token ; (* Check that token_owner exists *) - ledger_get_exn ledger - (Public_key.compress token_owner.public_key) - Token_id.default - |> ignore ; + ledger_get_exn ledger token_owner_pk Token_id.default |> ignore ; execute_zkapp_command_transaction token_minting ; check_token_balance token_account1 100 ; execute_zkapp_command_transaction token_transfers ; diff --git a/src/lib/mina_metrics/mina_metrics.mli b/src/lib/mina_metrics/mina_metrics.mli index 705c8f2e8c1..d6bd6b4fde4 100644 --- a/src/lib/mina_metrics/mina_metrics.mli +++ b/src/lib/mina_metrics/mina_metrics.mli @@ -90,6 +90,8 @@ module Transaction_pool : sig val transactions_added_to_pool : Counter.t + val vk_refcount_table_size : Gauge.t + val zkapp_transactions_added_to_pool : Counter.t val zkapp_transaction_size : Counter.t diff --git a/src/lib/mina_metrics/no_metrics/mina_metrics.ml b/src/lib/mina_metrics/no_metrics/mina_metrics.ml index a65c96f5677..8c32428ce10 100644 --- a/src/lib/mina_metrics/no_metrics/mina_metrics.ml +++ b/src/lib/mina_metrics/no_metrics/mina_metrics.ml @@ -100,6 +100,8 @@ module Transaction_pool = struct let transactions_added_to_pool : Counter.t = () + let vk_refcount_table_size : Gauge.t = () + let zkapp_transactions_added_to_pool : Counter.t = () let zkapp_transaction_size : Counter.t = () diff --git a/src/lib/mina_metrics/prometheus_metrics/mina_metrics.ml b/src/lib/mina_metrics/prometheus_metrics/mina_metrics.ml index f4d5c63db47..925d9801918 100644 --- a/src/lib/mina_metrics/prometheus_metrics/mina_metrics.ml +++ b/src/lib/mina_metrics/prometheus_metrics/mina_metrics.ml @@ -358,6 +358,10 @@ module Transaction_pool = struct in Counter.v "transactions_added_to_pool" ~help ~namespace ~subsystem + let vk_refcount_table_size : Gauge.t = + let help = "Size of verification key refcount table" in + Gauge.v "vk_refcount_table_size" ~help ~namespace ~subsystem + let zkapp_transactions_added_to_pool : Counter.t = let help = "Number of zkapp transactions added to the pool since the node start" diff --git a/src/lib/mina_wire_types/mina_base/mina_base_transaction_status.ml b/src/lib/mina_wire_types/mina_base/mina_base_transaction_status.ml index 2ee9ea98e24..64391ccc37a 100644 --- a/src/lib/mina_wire_types/mina_base/mina_base_transaction_status.ml +++ b/src/lib/mina_wire_types/mina_base/mina_base_transaction_status.ml @@ -40,6 +40,7 @@ module Failure = struct | Account_proved_state_precondition_unsatisfied | Account_is_new_precondition_unsatisfied | Protocol_state_precondition_unsatisfied + | Unexpected_verification_key_hash | Incorrect_nonce | Invalid_fee_excess | Cancelled diff --git a/src/lib/mina_wire_types/mina_base/mina_base_zkapp_command.ml b/src/lib/mina_wire_types/mina_base/mina_base_zkapp_command.ml index fb8099f2987..5b354e8b4c5 100644 --- a/src/lib/mina_wire_types/mina_base/mina_base_zkapp_command.ml +++ b/src/lib/mina_wire_types/mina_base/mina_base_zkapp_command.ml @@ -97,11 +97,7 @@ module Valid = struct end module V1 = struct - type t = - { zkapp_command : V1.t - ; verification_keys : - (Mina_base_account_id.V2.t * Verification_key_hash.V1.t) list - } + type t = { zkapp_command : V1.t } end end diff --git a/src/lib/mina_wire_types/mina_base/mina_base_zkapp_command.mli b/src/lib/mina_wire_types/mina_base/mina_base_zkapp_command.mli index af32761922d..d9a4dbf1c3e 100644 --- a/src/lib/mina_wire_types/mina_base/mina_base_zkapp_command.mli +++ b/src/lib/mina_wire_types/mina_base/mina_base_zkapp_command.mli @@ -99,11 +99,7 @@ module Valid : sig end module V1 : sig - type t = - { zkapp_command : V1.t - ; verification_keys : - (Mina_base_account_id.V2.t * Verification_key_hash.V1.t) list - } + type t = { zkapp_command : V1.t } end end diff --git a/src/lib/mina_wire_types/test/type_equalities.ml b/src/lib/mina_wire_types/test/type_equalities.ml index 9f09025cd4b..a16d5f56c10 100644 --- a/src/lib/mina_wire_types/test/type_equalities.ml +++ b/src/lib/mina_wire_types/test/type_equalities.ml @@ -198,10 +198,6 @@ module Mina_base = struct (O.Account_update.Body.Events'.Stable) (W.Account_update.Body.Events') include Assert_equal0V1 (O.Ledger_hash.Stable) (W.Ledger_hash) - include - Assert_equal0V1 - (O.Zkapp_command.Valid.Verification_key_hash.Stable) - (W.Zkapp_command.Valid.Verification_key_hash) include Assert_equal0V1 diff --git a/src/lib/network_peer/envelope.ml b/src/lib/network_peer/envelope.ml index 6a974ac0f0f..c76d9f467ae 100644 --- a/src/lib/network_peer/envelope.ml +++ b/src/lib/network_peer/envelope.ml @@ -91,6 +91,11 @@ module Incoming = struct let map ~f t = { t with data = f t.data } + let sequence_error t = + let open Result.Let_syntax in + let%map data = t.data in + { t with data } + let local data = let received_at = Time.now () in let sender = Sender.Local in diff --git a/src/lib/network_peer/envelope.mli b/src/lib/network_peer/envelope.mli index 1a2dc389424..ed5729a0464 100644 --- a/src/lib/network_peer/envelope.mli +++ b/src/lib/network_peer/envelope.mli @@ -22,6 +22,8 @@ module Incoming : sig val map : f:('a -> 'b) -> 'a t -> 'b t + val sequence_error : ('a, 'e) Result.t t -> ('a t, 'e) Result.t + val local : 'a -> 'a t val remote_sender_exn : 'a t -> Peer.t diff --git a/src/lib/network_pool/transaction_pool.ml b/src/lib/network_pool/transaction_pool.ml index 0f5e00b733e..83a77afe8ea 100644 --- a/src/lib/network_pool/transaction_pool.ml +++ b/src/lib/network_pool/transaction_pool.ml @@ -327,6 +327,46 @@ struct t end + module Vk_refcount_table = struct + type t = (int * Verification_key_wire.t) Zkapp_basic.F_map.Table.t + + let create () = Zkapp_basic.F_map.Table.create () + + let find t key = Zkapp_basic.F_map.Table.find t key + + let inc ~key ~value t = + Zkapp_basic.F_map.Table.update t key ~f:(function + | None -> + (1, value) + | Some (x, value) -> + (x + 1, value) ) ; + Mina_metrics.( + Gauge.set Transaction_pool.vk_refcount_table_size + (Float.of_int (Zkapp_basic.F_map.Table.length t))) + + let dec ~key ~value:_ t = + Zkapp_basic.F_map.Table.change t key ~f:(function + | None -> + None + | Some (x, value) -> + if x = 1 then None else Some (x - 1, value) ) ; + Mina_metrics.( + Gauge.set Transaction_pool.vk_refcount_table_size + (Float.of_int (Zkapp_basic.F_map.Table.length t))) + + let lift_common t table_modify cmd = + User_command.extract_vks cmd + |> List.iter ~f:(fun vk -> table_modify ~key:vk.hash ~value:vk t) + + let lift1 t table_modify cmd = + Transaction_hash.User_command_with_valid_signature.forget_check cmd + |> With_hash.data |> lift_common t table_modify + + let lift2 t table_modify (cmd : User_command.Valid.t With_status.t) = + With_status.data cmd |> User_command.forget_check + |> lift_common t table_modify + end + type t = { mutable pool : Indexed_pool.t ; recently_seen : (Lru_cache.t[@sexp.opaque]) @@ -335,7 +375,7 @@ struct , Time.t * [ `Batch of int ] ) Hashtbl.t (** Commands generated on this machine, that are not included in the - current best tip, along with the time they were added. *) + current best tip, along with the time they were added. *) ; locally_generated_committed : ( Transaction_hash.User_command_with_valid_signature.t , Time.t * [ `Batch of int ] ) @@ -348,6 +388,7 @@ struct ; batcher : Batcher.t ; mutable best_tip_diff_relay : (unit Deferred.t[@sexp.opaque]) Option.t ; mutable best_tip_ledger : (Base_ledger.t[@sexp.opaque]) Option.t + ; verification_key_table : (Vk_refcount_table.t[@sexp.opaque]) } [@@deriving sexp_of] @@ -461,7 +502,12 @@ struct The locally generated commands need to move from locally_generated_uncommitted to locally_generated_committed and vice versa so those hashtables remain in sync with reality. + + Don't forget to modify the refcount table as well as remove from the + index pool. *) + let vk_table_lift1 = Vk_refcount_table.lift1 t.verification_key_table in + let vk_table_lift2 = Vk_refcount_table.lift2 t.verification_key_table in let global_slot = Indexed_pool.global_slot_since_genesis t.pool in t.best_tip_ledger <- Some best_tip_ledger ; let pool_max_size = t.config.pool_max_size in @@ -477,6 +523,8 @@ struct ] @ metadata ) in + List.iter new_commands ~f:(vk_table_lift2 Vk_refcount_table.inc) ; + List.iter removed_commands ~f:(vk_table_lift2 Vk_refcount_table.dec) ; [%log' trace t.logger] ~metadata: [ ( "removed" @@ -541,6 +589,8 @@ struct Transaction_hash.User_command_with_valid_signature .to_yojson locally_generated_dropped ) ) ] ; + List.iter locally_generated_dropped + ~f:(vk_table_lift1 Vk_refcount_table.dec) ; let pool'', dropped_commands = let accounts_to_check = List.fold (new_commands @ removed_commands) ~init:Account_id.Set.empty @@ -601,17 +651,19 @@ struct |> Option.is_some ) in if not @@ List.is_empty commit_conflicts_locally_generated then - [%log' info t.logger] - "Locally generated commands $cmds dropped because they conflicted \ - with a committed command." - ~metadata: - [ ( "cmds" - , `List - (List.map commit_conflicts_locally_generated - ~f: - Transaction_hash.User_command_with_valid_signature - .to_yojson ) ) - ] ; + List.iter commit_conflicts_locally_generated + ~f:(vk_table_lift1 Vk_refcount_table.dec) ; + [%log' info t.logger] + "Locally generated commands $cmds dropped because they conflicted with \ + a committed command." + ~metadata: + [ ( "cmds" + , `List + (List.map commit_conflicts_locally_generated + ~f: + Transaction_hash.User_command_with_valid_signature + .to_yojson ) ) + ] ; [%log' debug t.logger] !"Finished handling diff. Old pool size %i, new pool size %i. Dropped \ %i commands during backtracking to maintain max size." @@ -626,6 +678,7 @@ struct be in locally_generated_committed. If it wasn't, try re-adding to the pool. *) let remove_cmd () = + vk_table_lift1 Vk_refcount_table.dec cmd ; assert ( Option.is_some @@ Hashtbl.find_and_remove t.locally_generated_uncommitted cmd ) @@ -682,6 +735,7 @@ struct , Transaction_hash.User_command_with_valid_signature .to_yojson cmd ) ] ; + vk_table_lift1 Vk_refcount_table.inc cmd ; Mina_metrics.( Gauge.set Transaction_pool.pool_size (Float.of_int (Indexed_pool.size pool'''))) ; @@ -700,6 +754,7 @@ struct , Transaction_hash.User_command_with_valid_signature.to_yojson cmd ) ] ; + vk_table_lift1 Vk_refcount_table.dec cmd ; ignore ( Hashtbl.find_and_remove t.locally_generated_uncommitted cmd : (Time.t * [ `Batch of int ]) option ) ) ; @@ -731,6 +786,7 @@ struct ; best_tip_diff_relay = None ; recently_seen = Lru_cache.Q.create () ; best_tip_ledger = None + ; verification_key_table = Vk_refcount_table.create () } in don't_wait_for @@ -972,6 +1028,7 @@ struct | _ -> () + (** DO NOT mutate any transaction pool state in this function, you may only mutate in the synchronous `apply` function. *) let verify (t : pool) (diff : t Envelope.Incoming.t) : verified Envelope.Incoming.t Deferred.Or_error.t = let open Deferred.Or_error.Let_syntax in @@ -1052,17 +1109,25 @@ struct "We don't have a transition frontier at the moment, so we're \ unable to verify any transactions." in + let%bind diff' = O1trace.sync_thread "convert_transactions_to_verifiable" (fun () -> - Or_error.try_with (fun () -> - Envelope.Incoming.map diff - ~f: - (List.map ~f:(fun cmd -> - User_command.to_verifiable ~ledger + Envelope.Incoming.map diff + ~f: + (User_command.Any.to_all_verifiable + ~find_vk:(fun vk_hash account_id -> + match + Vk_refcount_table.find t.verification_key_table vk_hash + with + | Some (_, vk) -> + Ok vk + | None -> + Zkapp_command.Verifiable.find_vk_via_ledger ~ledger ~get:Base_ledger.get ~location_of_account: - Base_ledger.location_of_account cmd - |> Or_error.ok_exn ) ) ) ) + Base_ledger.location_of_account vk_hash + account_id ) ) ) + |> Envelope.Incoming.sequence_error |> Result.map_error ~f:(Error.tag ~tag:"Verification_failed") |> Deferred.return in @@ -1127,6 +1192,7 @@ struct in (Time.now (), `Batch batch_num) ) + (* This must be synchronous, but you MAY modify state here (do not modify pool state in `verify` *) let apply t (diff : verified Envelope.Incoming.t) = let open Or_error.Let_syntax in let is_sender_local = @@ -1200,6 +1266,13 @@ struct | Error err -> (pool, Error (cmd, err)) ) in + let added_cmds = + List.filter_map add_results ~f:(function + | Ok (cmd, _) -> + Some cmd + | Error _ -> + None ) + in let dropped_for_add = List.filter_map add_results ~f:(function | Ok (_, dropped) -> @@ -1217,6 +1290,13 @@ struct in (* handle drops of locally generated commands *) let all_dropped_cmds = dropped_for_add @ dropped_for_size in + + (* apply changes to the vk-refcount-table here *) + let () = + let lift = Vk_refcount_table.lift1 t.verification_key_table in + List.iter added_cmds ~f:(lift Vk_refcount_table.inc) ; + List.iter all_dropped_cmds ~f:(lift Vk_refcount_table.dec) + in let all_dropped_cmd_hashes = List.map all_dropped_cmds ~f:Transaction_hash.User_command_with_valid_signature.hash @@ -1595,30 +1675,35 @@ let%test_module _ = let replace_valid_zkapp_command_authorizations ~keymap ~ledger valid_cmds : User_command.Valid.t list Deferred.t = - Deferred.List.map - (valid_cmds : User_command.Valid.t list) - ~f:(function - | Zkapp_command zkapp_command_dummy_auths -> - let%map zkapp_command = - Zkapp_command_builder.replace_authorizations ~keymap ~prover - (Zkapp_command.Valid.forget zkapp_command_dummy_auths) - in - let valid_zkapp_command = - let open Mina_ledger.Ledger in - match - Zkapp_command.Valid.to_valid ~ledger ~get ~location_of_account - zkapp_command - with - | Ok ps -> - ps - | Error err -> - Error.raise - @@ Error.tag ~tag:"Could not create Zkapp_command.Valid.t" - err - in - User_command.Zkapp_command valid_zkapp_command - | Signed_command _ -> - failwith "Expected Zkapp_command valid user command" ) + let open Deferred.Let_syntax in + let%map zkapp_commands_fixed = + Deferred.List.map + (valid_cmds : User_command.Valid.t list) + ~f:(function + | Zkapp_command zkapp_command_dummy_auths -> + let%map cmd = + Zkapp_command_builder.replace_authorizations ~keymap ~prover + (Zkapp_command.Valid.forget zkapp_command_dummy_auths) + in + User_command.Zkapp_command cmd + | Signed_command _ -> + failwith "Expected Zkapp_command valid user command" ) + in + match + User_command.Any.to_all_verifiable zkapp_commands_fixed + ~find_vk: + (Zkapp_command.Verifiable.find_vk_via_ledger ~ledger + ~get:Mina_ledger.Ledger.get + ~location_of_account:Mina_ledger.Ledger.location_of_account ) + |> Or_error.bind ~f:(fun xs -> + List.map xs ~f:User_command.check_verifiable + |> Or_error.combine_errors ) + with + | Ok cmds -> + cmds + | Error err -> + Error.raise + @@ Error.tag ~tag:"Could not create Zkapp_command.Valid.t" err (** Assert the invariants of the locally generated command tracking system. *) let assert_locally_generated (pool : Test.Resource_pool.t) = @@ -1793,10 +1878,12 @@ let%test_module _ = in let zkapp_command = Or_error.ok_exn - (Zkapp_command.Valid.to_valid ~ledger:() - ~get:(fun _ _ -> failwith "Not expecting proof zkapp_command") - ~location_of_account:(fun _ _ -> - failwith "Not expecting proof zkapp_command" ) + (Zkapp_command.Valid.to_valid + ~find_vk: + (Zkapp_command.Verifiable.find_vk_via_ledger ~ledger:() + ~get:(fun _ _ -> failwith "Not expecting proof zkapp_command") + ~location_of_account:(fun _ _ -> + failwith "Not expecting proof zkapp_command" ) ) zkapp_command ) in User_command.Zkapp_command zkapp_command @@ -1879,76 +1966,20 @@ let%test_module _ = } ) } in + let zkapp_command_valid_vk_hashes = + Zkapp_command.For_tests.replace_vks zkapp_command vk + in let valid_zkapp_command = Or_error.ok_exn - (Zkapp_command.Valid.to_valid ~ledger:best_tip_ledger - ~get:Mina_ledger.Ledger.get - ~location_of_account:Mina_ledger.Ledger.location_of_account - zkapp_command ) - in - let valid_zkapp_command_valid_vk_hashes = - Zkapp_command.Valid.For_tests.replace_zkapp_command - valid_zkapp_command - { (Zkapp_command.Valid.forget valid_zkapp_command) with - account_updates = - Zkapp_command.Call_forest.map zkapp_command.account_updates - ~f:(fun (p : Account_update.t) -> - { p with - body = - { p.body with - authorization_kind = - (* replace dummy vk hashes *) - ( match p.body.authorization_kind with - | Proof _vk_hash -> - let account_id = - Account_id.create p.body.public_key - p.body.token_id - in - let account = - match - Mina_ledger.Ledger.location_of_account - best_tip_ledger account_id - with - | Some loc -> ( - match - Mina_ledger.Ledger.get - best_tip_ledger loc - with - | Some acct -> - acct - | None -> - failwith - "Expected to find account in \ - ledger" ) - | None -> - failwith - "Expected to find local for \ - account id in ledger" - in - let vk_hash = - match account.zkapp with - | Some zkapp -> ( - match zkapp.verification_key with - | Some vk -> - With_hash.hash vk - | None -> - failwith - "Expected to find verification \ - key for Proof authorization \ - kind" ) - | None -> - failwith - "Expected to find zkApp account \ - for Proof authorization kind" - in - Proof vk_hash - | ak -> - ak ) - } - } ) - } + (Zkapp_command.Valid.to_valid + ~find_vk: + (Zkapp_command.Verifiable.find_vk_via_ledger + ~ledger:best_tip_ledger ~get:Mina_ledger.Ledger.get + ~location_of_account: + Mina_ledger.Ledger.location_of_account ) + zkapp_command_valid_vk_hashes ) in - User_command.Zkapp_command valid_zkapp_command_valid_vk_hashes + User_command.Zkapp_command valid_zkapp_command in go (n + 1) (cmd :: cmds) in diff --git a/src/lib/staged_ledger/staged_ledger.ml b/src/lib/staged_ledger/staged_ledger.ml index c1864ed1d54..d88d7a500ce 100644 --- a/src/lib/staged_ledger/staged_ledger.ml +++ b/src/lib/staged_ledger/staged_ledger.ml @@ -1020,11 +1020,10 @@ module T = struct let check_commands ledger ~verifier (cs : User_command.t list) = let open Deferred.Or_error.Let_syntax in let%bind cs = - Or_error.try_with (fun () -> - List.map cs ~f:(fun cmd -> - let open Ledger in - User_command.to_verifiable ~ledger ~get ~location_of_account cmd - |> Or_error.ok_exn ) ) + User_command.Last.to_all_verifiable cs + ~find_vk: + (Zkapp_command.Verifiable.find_vk_via_ledger ~ledger ~get:Ledger.get + ~location_of_account:Ledger.location_of_account ) |> Deferred.return in let%map xs = Verifier.verify_commands verifier cs in @@ -1788,82 +1787,6 @@ module T = struct epoch_ledger |> not - let validate_account_update_proofs ~logger ~validating_ledger - (txn : User_command.Valid.t) = - let open Result.Let_syntax in - let get_verification_keys account_ids = - List.fold_until account_ids ~init:Account_id.Map.empty - ~f:(fun acc id -> - let get_vk () = - let open Option.Let_syntax in - let%bind loc = - Transaction_snark.Transaction_validator.Hashless_ledger - .location_of_account validating_ledger id - in - let%bind account = - Transaction_snark.Transaction_validator.Hashless_ledger.get - validating_ledger loc - in - let%bind zkapp = account.zkapp in - let%map vk = zkapp.verification_key in - vk.hash - in - match get_vk () with - | Some vk -> - Continue (Account_id.Map.update acc id ~f:(fun _ -> vk)) - | None -> - [%log error] - ~metadata:[ ("account_id", Account_id.to_yojson id) ] - "Staged_ledger_diff creation: Verification key not found for \ - account_update with proof authorization and account_id \ - $account_id" ; - Stop Account_id.Map.empty ) - ~finish:Fn.id - in - match txn with - | Zkapp_command p -> - let%map checked_verification_keys = - Account_id.Map.of_alist_or_error p.verification_keys - in - let proof_zkapp_command = - Zkapp_command.Call_forest.fold ~init:Account_id.Set.empty - p.zkapp_command.account_updates ~f:(fun acc p -> - if - Control.(Tag.equal Proof (tag (Account_update.authorization p))) - then Account_id.Set.add acc (Account_update.account_id p) - else acc ) - in - let current_verification_keys = - get_verification_keys (Account_id.Set.to_list proof_zkapp_command) - in - if - Account_id.Set.length proof_zkapp_command - = Account_id.Map.length checked_verification_keys - && Account_id.Map.equal - Zkapp_command.Valid.Verification_key_hash.equal - checked_verification_keys current_verification_keys - then true - else ( - [%log error] - ~metadata: - [ ( "checked_verification_keys" - , [%to_yojson: - (Account_id.t * Zkapp_command.Valid.Verification_key_hash.t) - list] - (Account_id.Map.to_alist checked_verification_keys) ) - ; ( "current_verification_keys" - , [%to_yojson: - (Account_id.t * Zkapp_command.Valid.Verification_key_hash.t) - list] - (Account_id.Map.to_alist current_verification_keys) ) - ] - "Staged_ledger_diff creation: Verifcation keys used for verifying \ - proofs $checked_verification_keys and verification keys in the \ - ledger $current_verification_keys don't match" ; - false ) - | _ -> - Ok true - let create_diff ~(constraint_constants : Genesis_constants.Constraint_constants.t) ?(log_block_creation = false) t ~coinbase_receiver ~logger @@ -1948,14 +1871,6 @@ module T = struct match O1trace.sync_thread "validate_transaction_against_staged_ledger" (fun () -> - let%bind valid_proofs = - validate_account_update_proofs ~logger ~validating_ledger - txn - in - let%bind () = - if valid_proofs then Ok () - else Or_error.errorf "Verification key mismatch" - in Transaction_validator.apply_transaction ~constraint_constants validating_ledger ~txn_state_view:current_state_view @@ -2545,9 +2460,12 @@ let%test_module "staged ledger tests" = in let valid_zkapp_command_with_auths : Zkapp_command.Valid.t = match - Zkapp_command.Valid.to_valid zkapp_command_with_auths ~ledger - ~get:Ledger.get - ~location_of_account:Ledger.location_of_account + Zkapp_command.Valid.to_valid + ~find_vk: + (Zkapp_command.Verifiable.find_vk_via_ledger ~ledger + ~get:Ledger.get + ~location_of_account:Ledger.location_of_account ) + zkapp_command_with_auths with | Ok ps -> ps @@ -3784,6 +3702,8 @@ let%test_module "staged ledger tests" = | Error _ -> assert false ) ) ) + (* TODO: Re-enable after https://github.com/MinaProtocol/mina/pull/12397 *) + (* let%test_unit "Mismatched verification keys in zkApp accounts and \ transactions" = let open Transaction_snark.For_tests in @@ -3868,9 +3788,11 @@ let%test_module "staged ledger tests" = in let valid_zkapp_command = Or_error.ok_exn - (Zkapp_command.Valid.to_valid ~ledger:valid_against_ledger - ~get:Ledger.get - ~location_of_account:Ledger.location_of_account + (Zkapp_command.Valid.to_valid + ~find_vk: + (Zkapp_command.Verifiable.find_vk_via_ledger + ~ledger:valid_against_ledger ~get:Ledger.get + ~location_of_account:Ledger.location_of_account ) zkapp_command ) in ignore @@ -3925,5 +3847,5 @@ let%test_module "staged ledger tests" = ( Transaction_status.Failure.Collection.to_yojson tbl |> Yojson.Safe.to_string ) ) | _ -> - failwith "expecting zkapp_command transaction" ) ) ) + failwith "expecting zkapp_command transaction" ) ) ) *) end ) diff --git a/src/lib/transaction_logic/mina_transaction_logic.ml b/src/lib/transaction_logic/mina_transaction_logic.ml index 563ada08279..abff05c0876 100644 --- a/src/lib/transaction_logic/mina_transaction_logic.ml +++ b/src/lib/transaction_logic/mina_transaction_logic.ml @@ -1009,6 +1009,8 @@ module Make (L : Ledger_intf.S) : S with type ledger := L.t = struct type t = Snark_params.Tick.Field.t let if_ = value_if + + let equal = Snark_params.Tick.Field.equal end module Bool = struct @@ -1214,6 +1216,12 @@ module Make (L : Ledger_intf.S) : S with type ledger := L.t = struct let if_ = value_if end + module Verification_key_hash = struct + type t = Field.t option + + let equal vk1 vk2 = Option.equal Field.equal vk1 vk2 + end + module Actions = struct type t = Zkapp_account.Actions.t @@ -1349,6 +1357,13 @@ module Make (L : Ledger_intf.S) : S with type ledger := L.t = struct let set_verification_key verification_key (a : t) = set_zkapp a ~f:(fun zkapp -> { zkapp with verification_key }) + let verification_key_hash (a : t) = + match a.zkapp with + | None -> + None + | Some zkapp -> + Option.map zkapp.verification_key ~f:With_hash.hash + let last_sequence_slot (a : t) = (get_zkapp a).last_sequence_slot let set_last_sequence_slot last_sequence_slot (a : t) = @@ -1494,6 +1509,13 @@ module Make (L : Ledger_intf.S) : S with type ledger := L.t = struct | Proof _ | None_given -> false + let verification_key_hash (p : t) = + match p.body.authorization_kind with + | Proof vk_hash -> + Some vk_hash + | _ -> + None + module Update = struct open Zkapp_basic diff --git a/src/lib/transaction_logic/zkapp_command_logic.ml b/src/lib/transaction_logic/zkapp_command_logic.ml index 28ba61028f0..a885829c748 100644 --- a/src/lib/transaction_logic/zkapp_command_logic.ml +++ b/src/lib/transaction_logic/zkapp_command_logic.ml @@ -133,6 +133,14 @@ module type Global_slot_intf = sig val equal : t -> t -> bool end +module type Verification_key_hash_intf = sig + type t + + type bool + + val equal : t -> t -> bool +end + module type Timing_intf = sig include Iffable @@ -304,6 +312,8 @@ module type Account_update_intf = sig type nonce + type verification_key_hash + type _ or_ignore val balance_change : t -> signed_amount @@ -332,6 +342,8 @@ module type Account_update_intf = sig val is_proved : t -> bool + val verification_key_hash : t -> verification_key_hash + module Update : sig type _ set_or_keep @@ -543,10 +555,10 @@ module type Account_intf = sig val set_receipt_chain_hash : t -> receipt_chain_hash -> t - (** Fill the snapp field of the account if it's currently [None] *) + (** Fill the zkapp field of the account if it's currently [None] *) val make_zkapp : t -> t - (** If the current account has no snapp fields set, reset its snapp field to + (** If the current account has no zkApp fields set, reset its zkapp field to [None]. *) val unmake_zkapp : t -> t @@ -569,6 +581,10 @@ module type Account_intf = sig val set_verification_key : verification_key -> t -> t + type verification_key_hash + + val verification_key_hash : t -> verification_key_hash + val last_sequence_slot : t -> global_slot val set_last_sequence_slot : global_slot -> t -> t @@ -690,8 +706,6 @@ module type Inputs_intf = sig module Timing : Timing_intf with type bool := Bool.t and type global_slot := Global_slot.t - module Verification_key : Iffable with type bool := Bool.t - module Zkapp_uri : Iffable with type bool := Bool.t module Token_symbol : Iffable with type bool := Bool.t @@ -704,6 +718,10 @@ module type Inputs_intf = sig and type transaction_commitment := Transaction_commitment.t and type index := Index.t) + and Verification_key : (Iffable with type bool := Bool.t) + and Verification_key_hash : + (Verification_key_hash_intf with type bool := Bool.t) + and Account : (Account_intf with type Permissions.controller := Controller.t @@ -714,6 +732,7 @@ module type Inputs_intf = sig and type global_slot := Global_slot.t and type field := Field.t and type verification_key := Verification_key.t + and type verification_key_hash := Verification_key_hash.t and type zkapp_uri := Zkapp_uri.t and type token_symbol := Token_symbol.t and type public_key := Public_key.t @@ -735,6 +754,7 @@ module type Inputs_intf = sig and type public_key := Public_key.t and type nonce := Nonce.t and type account_id := Account_id.t + and type verification_key_hash := Verification_key_hash.t and type Update.timing := Timing.t and type 'a Update.set_or_keep := 'a Set_or_keep.t and type Update.field := Field.t @@ -884,7 +904,7 @@ module Make (Inputs : Inputs_intf) = struct } let get_next_account_update (current_forest : Stack_frame.t) - (* The stack for the most recent snapp *) + (* The stack for the most recent zkApp *) (call_stack : Call_stack.t) (* The partially-completed parent stacks *) : get_next_account_update_result = (* If the current stack is complete, 'return' to the previous @@ -1142,6 +1162,17 @@ module Make (Inputs : Inputs_intf) = struct (Account_update.token_id account_update) (a, inclusion_proof) in + let matching_verification_key_hashes = + Inputs.Bool.( + (not (Account_update.is_proved account_update)) + ||| Verification_key_hash.equal + (Account.verification_key_hash a) + (Account_update.verification_key_hash account_update)) + in + let local_state = + Local_state.add_check local_state Unexpected_verification_key_hash + matching_verification_key_hashes + in let local_state = h.perform (Check_account_precondition @@ -1334,11 +1365,11 @@ module Make (Inputs : Inputs_intf) = struct (* The [proved_state] tracks whether the app state has been entirely determined by proofs ([true] if so), to allow zkApp authors to be confident that their initialization logic has been run, rather than - some malicious deployer instantiating the snapp in an account with + some malicious deployer instantiating the zkApp in an account with some fake non-initial state. The logic here is: * if the state is unchanged, keep the previous value; - * if the state has been entriely replaced, and the authentication + * if the state has been entirely replaced, and the authentication was a proof, the state has been 'proved' and [proved_state] is set to [true]; * if the state has been partially updated by a proof, the diff --git a/src/lib/transaction_snark/test/multisig_account/multisig_account.ml b/src/lib/transaction_snark/test/multisig_account/multisig_account.ml index 2e1744d3cb4..1112d8685c9 100644 --- a/src/lib/transaction_snark/test/multisig_account/multisig_account.ml +++ b/src/lib/transaction_snark/test/multisig_account/multisig_account.ml @@ -366,8 +366,7 @@ let%test_module "multisig_account" = } ; use_full_commitment = false ; caller = Call - ; authorization_kind = - Proof (Mina_base.Zkapp_account.dummy_vk_hash ()) + ; authorization_kind = Proof (With_hash.hash vk) } ; authorization = Proof Mina_base.Proof.transaction_dummy } diff --git a/src/lib/transaction_snark/test/zkapps_examples/actions/actions.ml b/src/lib/transaction_snark/test/zkapps_examples/actions/actions.ml index 4a56592477b..94aa9a98667 100644 --- a/src/lib/transaction_snark/test/zkapps_examples/actions/actions.ml +++ b/src/lib/transaction_snark/test/zkapps_examples/actions/actions.ml @@ -95,7 +95,10 @@ let%test_module "Sequence events test" = end let test_zkapp_command ?expected_failure ?state_body ?(fee_payer_nonce = 0) - ~ledger zkapp_command = + ~ledger zkapp_command0 = + let zkapp_command = + Zkapps_examples.patch_verification_key_hashes ~ledger zkapp_command0 + in let memo = Signed_command_memo.empty in let transaction_commitment : Zkapp_command.Transaction_commitment.t = let account_updates_hash = diff --git a/src/lib/transaction_snark/test/zkapps_examples/add_events/add_events.ml b/src/lib/transaction_snark/test/zkapps_examples/add_events/add_events.ml index 75222499b57..2a03501b746 100644 --- a/src/lib/transaction_snark/test/zkapps_examples/add_events/add_events.ml +++ b/src/lib/transaction_snark/test/zkapps_examples/add_events/add_events.ml @@ -94,7 +94,10 @@ let%test_module "Add events test" = ~handler:(Zkapps_add_events.update_events_handler events) ) end - let test_zkapp_command ?expected_failure zkapp_command = + let test_zkapp_command ?expected_failure zkapp_command0 = + let zkapp_command = + Zkapps_examples.patch_verification_key_hashes zkapp_command0 + in let memo = Signed_command_memo.empty in let transaction_commitment : Zkapp_command.Transaction_commitment.t = let account_updates_hash = diff --git a/src/lib/transaction_snark/test/zkapps_examples/calls/calls.ml b/src/lib/transaction_snark/test/zkapps_examples/calls/calls.ml index 22b5b5e0d57..db3fa0ebdcc 100644 --- a/src/lib/transaction_snark/test/zkapps_examples/calls/calls.ml +++ b/src/lib/transaction_snark/test/zkapps_examples/calls/calls.ml @@ -211,8 +211,10 @@ let%test_module "Composability test" = |> fst end - let test_zkapp_command ?expected_failure zkapp_command = - let memo = Signed_command_memo.empty in + let test_zkapp_command ?expected_failure zkapp_command0 = + let zkapp_command = + Zkapps_examples.patch_verification_key_hashes zkapp_command0 + in let transaction_commitment : Zkapp_command.Transaction_commitment.t = (* TODO: This is a pain. *) let account_updates_hash = @@ -229,6 +231,7 @@ let%test_module "Composability test" = ; authorization = Signature.dummy } in + let memo = Signed_command_memo.empty in let memo_hash = Signed_command_memo.hash memo in let full_commitment = Zkapp_command.Transaction_commitment.create_complete diff --git a/src/lib/transaction_snark/test/zkapps_examples/empty_update/empty_update.ml b/src/lib/transaction_snark/test/zkapps_examples/empty_update/empty_update.ml index b286d98f4ed..f46e8c1e00c 100644 --- a/src/lib/transaction_snark/test/zkapps_examples/empty_update/empty_update.ml +++ b/src/lib/transaction_snark/test/zkapps_examples/empty_update/empty_update.ml @@ -68,6 +68,7 @@ let account_updates = [] |> Zkapp_command.Call_forest.cons_tree account_update |> Zkapp_command.Call_forest.cons deploy_account_update + |> Zkapps_examples.patch_verification_key_hashes let memo = Signed_command_memo.empty diff --git a/src/lib/transaction_snark/test/zkapps_examples/initialize_state/initialize_state.ml b/src/lib/transaction_snark/test/zkapps_examples/initialize_state/initialize_state.ml index d0d1fb69362..68971db9dd4 100644 --- a/src/lib/transaction_snark/test/zkapps_examples/initialize_state/initialize_state.ml +++ b/src/lib/transaction_snark/test/zkapps_examples/initialize_state/initialize_state.ml @@ -102,7 +102,10 @@ let%test_module "Initialize state test" = ~handler:(Zkapps_initialize_state.update_state_handler new_state) ) end - let test_zkapp_command ?expected_failure zkapp_command = + let test_zkapp_command ?expected_failure zkapp_command0 = + let zkapp_command = + Zkapps_examples.patch_verification_key_hashes zkapp_command0 + in let memo = Signed_command_memo.empty in let transaction_commitment : Zkapp_command.Transaction_commitment.t = (* TODO: This is a pain. *) diff --git a/src/lib/transaction_snark/transaction_snark.ml b/src/lib/transaction_snark/transaction_snark.ml index b4645a4e54e..2c7097dc10b 100644 --- a/src/lib/transaction_snark/transaction_snark.ml +++ b/src/lib/transaction_snark/transaction_snark.ml @@ -1171,6 +1171,12 @@ module Make_str (A : Wire_types.Concrete) = struct Zkapp_basic.Flagged_option.if_ ~if_:Data_as_hash.if_ b ~then_ ~else_ end + module Verification_key_hash = struct + type t = Field.t + + let equal = Field.equal + end + module Actions = struct type t = Zkapp_account.Actions.var @@ -1311,6 +1317,10 @@ module Make_str (A : Wire_types.Concrete) = struct ; hash } + let verification_key_hash (a : t) : Verification_key_hash.t = + verification_key a |> Zkapp_basic.Flagged_option.data + |> Data_as_hash.hash + let last_sequence_slot (a : t) = a.data.zkapp.last_sequence_slot let set_last_sequence_slot last_sequence_slot ({ data = a; hash } : t) @@ -1915,6 +1925,9 @@ module Make_str (A : Wire_types.Concrete) = struct let is_signed ({ account_update; _ } : t) = account_update.data.authorization_kind.is_signed + let verification_key_hash ({ account_update; _ } : t) = + account_update.data.authorization_kind.verification_key_hash + module Update = struct open Zkapp_basic diff --git a/src/lib/verifier/common.ml b/src/lib/verifier/common.ml index d6875fbfa55..4148faa7c61 100644 --- a/src/lib/verifier/common.ml +++ b/src/lib/verifier/common.ml @@ -128,8 +128,7 @@ let check : let v : User_command.Valid.t = (* Verification keys should be present if it reaches here *) let zkapp_command = - Or_error.ok_exn - (Zkapp_command.Valid.of_verifiable zkapp_command_with_vk) + Zkapp_command.Valid.of_verifiable zkapp_command_with_vk in User_command.Poly.Zkapp_command zkapp_command in diff --git a/src/lib/zkapps_examples/dune b/src/lib/zkapps_examples/dune index 8196a2c352c..11bb53af77e 100644 --- a/src/lib/zkapps_examples/dune +++ b/src/lib/zkapps_examples/dune @@ -14,6 +14,8 @@ kimchi_pasta kimchi_backend.pasta.basic mina_base + mina_base.import + mina_ledger pickles pickles.backend pickles_types diff --git a/src/lib/zkapps_examples/zkapps_examples.ml b/src/lib/zkapps_examples/zkapps_examples.ml index 9c159b602dc..dc9ec81869e 100644 --- a/src/lib/zkapps_examples/zkapps_examples.ml +++ b/src/lib/zkapps_examples/zkapps_examples.ml @@ -261,10 +261,11 @@ module Account_update_under_construction = struct ; use_full_commitment = Boolean.false_ ; caller = t.caller ; authorization_kind = - (* TODO: is there a valid vk hash available? *) { is_signed = Boolean.false_ ; is_proved = Boolean.true_ - ; verification_key_hash = Field.zero + ; verification_key_hash = + Field.zero + (* the vk hash is a dummy, to be patched with `patch_verification_key_hashes`, below *) } } in @@ -597,3 +598,44 @@ let compile : go provers in (tag, cache_handle, proof, provers) + +(* replace dummy vk hashes in account updates *) +let patch_verification_key_hashes ?ledger account_updates = + (* update vk hashes if Set in an account update *) + let vk_hash_tbl : Impl.field Public_key.Compressed.Table.t = + Public_key.Compressed.Table.create () + in + (* if ledger provided, add vk hashes from those accounts *) + Option.iter ledger ~f:(fun (ledger : Mina_ledger.Ledger.t) -> + Mina_ledger.Ledger.iteri ledger ~f:(fun _n acct -> + Option.iter acct.zkapp ~f:(fun zkapp -> + Option.iter zkapp.verification_key ~f:(fun vk -> + let pk : Public_key.Compressed.t = acct.public_key in + Public_key.Compressed.Table.set vk_hash_tbl ~key:pk + ~data:(With_hash.hash vk) ) ) ) ) ; + Zkapp_command.Call_forest.map account_updates + ~f:(fun (acct_update : Account_update.t) -> + let pk = acct_update.body.public_key in + let acct_update' = + match Public_key.Compressed.Table.find vk_hash_tbl pk with + | None -> + acct_update + | Some vk_hash -> ( + match acct_update.body.authorization_kind with + | Proof _ -> + { acct_update with + body = + { acct_update.body with authorization_kind = Proof vk_hash } + } + | Signature | None_given -> + acct_update ) + in + (* add entry for subsequent updates *) + ( match acct_update.body.update.verification_key with + | Set vk -> + let vk_hash = With_hash.hash vk in + Public_key.Compressed.Table.set vk_hash_tbl ~key:pk ~data:vk_hash + | Keep -> + () ) ; + acct_update' ) + |> Zkapp_command.Call_forest.accumulate_hashes'