Mina 证明实现过程
之前Mina项目研究的笔记
Snarker_worker 证明
Snarker worker 主要生成交易证明以及合并证明,所有在Scan State一颗子树上的所有交易状态能“合并”成一个证明。以4个交易为例,4个交易的状态“合并”到一个证明,证明从x0到x4的正确执行。
run_snark_worker
let%bind worker_state = Prod.Worker_state.create ~proof_level ()
创建过程为:
let create ~constraint_constants ~proof_level () =
let m =
match proof_level with
| Genesis_constants.Proof_level.Full ->
Some
( module Transaction_snark.Make (struct
let constraint_constants = constraint_constants
let proof_level = proof_level
end) : S )
| Check | None ->
None
in
Deferred.return { m; cache = Cache.create (); proof_level }
worker_state 的类型为:
type t =
{ m : (module S) option //其中 S = Transaction_snark.S
; cache : Cache.t
; proof_level : Genesis_constants.Proof_level.t
}
其中 cache 为存放 statement (key) 和 proof (value) 类型的Hash表缓存,size 为 100.
获取 public_key:
let public_key = fst (Lazy.force Mina_base.Sample_keypairs.keypairs).(0)
手续费:
fee = Currency.Fee.of_int 10
生成message:
message = Mina_base.Sok_message.create ~fee ~prover:public_key
message 的类型为:
type t =
{ fee : Currency.Fee.Stable.V1.t
; prover : Public_key.Compressed.Stable.V1.t
}
生成证明:
match Prod.perform_single worker_state ~message spec with
| Ok (proof, time) ->
Caml.Format.printf
!"Successfully proved in %{sexp: Time.Span.t}.@.Proof \
was:@.%{sexp: Transaction_snark.t}@."
time proof ;
exit 0
| Error err ->
Caml.Format.printf
!"Proving failed with error:@.%s"
(Error.to_string_hum err) ;
exit 1)
perform_single
计算Sok 消息的Hash 值:
let sok_digest = Mina_base.Sok_message.digest message
获取statement:
let statement = Work.Single.Spec.statement single
其中 single的类型为:
(* bin_io is for uptime service SNARK worker *)
type single_spec =
( Transaction.Stable.Latest.t
, Transaction_witness.Stable.Latest.t
, Transaction_snark.Stable.Latest.t )
Snark_work_lib.Work.Single.Spec.Stable.Latest.t
[@@deriving bin_io_unversioned, sexp]
type ('transition, 'witness, 'ledger_proof) t =
| Transition of //对应着Transition 类型
Transaction_snark.Statement.Stable.V1.t * 'transition * 'witness
| Merge of // 对应着Merge 类型
Transaction_snark.Statement.Stable.V1.t
* 'ledger_proof
* 'ledger_proof
[@@deriving sexp, to_yojson]
Work.Single.Spec.statement 函数定义为:
let statement = function Transition (s, _, _) -> s | Merge (s, _, _) -> s
返回值statement的类型为:Transaction_snark.Statement.Stable.V1.t
type ( 'ledger_hash
, 'amount
, 'pending_coinbase
, 'fee_excess
, 'token_id
, 'sok_digest )
t =
{ source : 'ledger_hash
; target : 'ledger_hash
; supply_increase : 'amount
; pending_coinbase_stack_state : 'pending_coinbase
; fee_excess : 'fee_excess
; next_available_token_before : 'token_id
; next_available_token_after : 'token_id
; sok_digest : 'sok_digest
}
[@@deriving compare, equal, hash, sexp, yojson, hlist]
对于Transition, 根据不同的交易类型,生成证明, 对于Merge类型,生成相就的Merge 证明。
match single with
| Work.Single.Spec.Transition // 生成交易的证明
(input, t, (w : Transaction_witness.t)) ->
process (fun () ->
let%bind t =
Deferred.return
@@
(* Validate the received transaction *)
match t with //匹配交易类型
| Command (Signed_command cmd) -> (
match Signed_command.check cmd with
| Some cmd ->
( Ok (Command (Signed_command cmd))
: Transaction.Valid.t Or_error.t )
| None ->
Or_error.errorf
"Command has an invalid signature" )
| Command (Parties _) ->
failwith "TODO"
| Fee_transfer ft ->
Ok (Fee_transfer ft)
| Coinbase cb ->
Ok (Coinbase cb)
in
Deferred.Or_error.try_with ~here:[%here] (fun () ->
M.of_transaction ~sok_digest ~snapp_account1:None
~snapp_account2:None
~source:input.Transaction_snark.Statement.source
~target:input.target
{ Transaction_protocol_state.Poly.transaction = t
; block_data = w.protocol_state_body
}
~init_stack:w.init_stack
~next_available_token_before:
input.next_available_token_before
~next_available_token_after:
input.next_available_token_after
~pending_coinbase_stack_state:
input
.Transaction_snark.Statement
.pending_coinbase_stack_state
(unstage (Mina_base.Sparse_ledger.handler w.ledger))))
| Merge (_, proof1, proof2) ->
process (fun () -> M.merge ~sok_digest proof1 proof2) ) // 生成Merge 的证明
Base proof
Transaction
Transaction.Stable.Latest.t的类型为:
type 'command t =
| Command of 'command
| Fee_transfer of Fee_transfer.Stable.V1.t
| Coinbase of Coinbase.Stable.V1.t
[@@deriving sexp, compare, equal, hash, yojson]
type t = User_command.Valid.Stable.V2.t Poly.Stable.V1.t
[@@deriving sexp, compare, equal, hash, yojson]
其中Command 类型为:
// Command类型:
type ('u, 's) t = Signed_command of 'u | Parties of 's
[@@deriving sexp, compare, equal, hash, yojson]
// Signed_command类型:
type ('payload, 'pk, 'signature) t =
{ payload : 'payload; signer : 'pk; signature : 'signature }
[@@deriving compare, sexp, hash, yojson, equal]
Parties类型为:
type t =
{ fee_payer : Party.Signed.Stable.V1.t
; other_parties : Party.Stable.V1.t list
; protocol_state : Snapp_predicate.Protocol_state.Stable.V1.t
}
[@@deriving sexp, compare, equal, hash, yojson]
Fee_Transfer交易类型为:
type t =
{ receiver_pk : Public_key.Compressed.Stable.V1.t // 接收者公钥
; fee : Currency.Fee.Stable.V1.t // 手续费用
; fee_token : Token_id.Stable.V1.t // 费用token
}
[@@deriving sexp, compare, equal, yojson, hash]
Coinbase交易类型:
type t =
{ receiver : Public_key.Compressed.Stable.V1.t
; amount : Currency.Amount.Stable.V1.t
; fee_transfer : Fee_transfer.Stable.V1.t option
}
[@@deriving sexp, compare, equal, hash, yojson]
Transaction_witness
Transaction_witness.Stable.Latest.t的类型为:
type t =
{ ledger : Mina_base.Sparse_ledger.Stable.V2.t
; protocol_state_body : Mina_state.Protocol_state.Body.Value.Stable.V1.t
; init_stack : Mina_base.Pending_coinbase.Stack_versioned.Stable.V1.t
; status : Mina_base.Transaction_status.Stable.V1.t
}
其中Mina_base.Sparse_ledger.Stable.V2.t 的类型为:
type t =
( Ledger_hash.Stable.V1.t
, Account_id.Stable.V1.t
, Account.Stable.V2.t
, Token_id.Stable.V1.t )
Sparse_ledger_lib.Sparse_ledger.T.Stable.V1.t
[@@deriving yojson, sexp]
// Sparse_ledger_lib.Sparse_ledger.T.Stable.V1.t类型为:
type ('hash, 'key, 'account, 'token_id) t =
{ indexes : ('key * int) list
; depth : int
; tree : ('hash, 'account) Tree.Stable.V1.t
; next_available_token : 'token_id
}
[@@deriving sexp, yojson]
Mina_state.Protocol_state.Body.Value.Stable.V1.t 类型为:
type ('state_hash, 'blockchain_state, 'consensus_state, 'constants) t =
{ genesis_state_hash : 'state_hash
; blockchain_state : 'blockchain_state
; consensus_state : 'consensus_state
; constants : 'constants
}
[@@deriving sexp, equal, compare, yojson, hash, version, hlist]
type t =
( State_hash.Stable.V1.t
, Blockchain_state.Value.Stable.V1.t
, Consensus.Data.Consensus_state.Value.Stable.V1.t
, Protocol_constants_checked.Value.Stable.V1.t )
Poly.Stable.V1.t
[@@deriving equal, ord, bin_io, hash, sexp, yojson, version]
Mina_base.Pending_coinbase.Stack_versioned.Stable.V1.t
type ('data_stack, 'state_stack) t =
{ data : 'data_stack; state : 'state_stack }
[@@deriving yojson, hash, sexp, equal, compare]
type t =
(Coinbase_stack.Stable.V1.t, State_stack.Stable.V1.t) Poly.Stable.V1.t
[@@deriving equal, yojson, hash, sexp, compare]
//Coinbase_stack.Stable.V1.t
type t = Field.t [@@deriving sexp, compare, hash, version { asserted }]
//State_stack.Stable.V1.t
type 'stack_hash t = { init : 'stack_hash; curr : 'stack_hash }
[@@deriving sexp, compare, hash, yojson, equal, hlist]
type t = Stack_hash.Stable.V1.t Poly.Stable.V1.t
[@@deriving sexp, compare, hash, equal, yojson]
Mina_base.Transaction_status.Stable.V1.t的类型为:
type t =
| Applied of Auxiliary_data.Stable.V1.t * Balance_data.Stable.V1.t
| Failed of Failure.Stable.V1.t * Balance_data.Stable.V1.t
[@@deriving sexp, yojson, equal, compare]
// Auxiliary_data.Stable.V1.t 类型:
type t =
{ fee_payer_account_creation_fee_paid :
Currency.Amount.Stable.V1.t option
; receiver_account_creation_fee_paid :
Currency.Amount.Stable.V1.t option
; created_token : Token_id.Stable.V1.t option
}
// Balance_data.Stable.V1.t 类型为:
type t =
{ fee_payer_balance : Currency.Balance.Stable.V1.t option
;
source_balance : Currency.Balance.Stable.V1.t option
; receiver_balance : Currency.Balance.Stable.V1.t option
}
[@@deriving sexp, yojson, equal, compare]
// Failure.Stable.V1.t 类型为:
type t =
| Predicate [@value 1]
| Source_not_present
| Receiver_not_present
| Amount_insufficient_to_create_account
| Cannot_pay_creation_fee_in_token
| Source_insufficient_balance
| Source_minimum_balance_violation
| Receiver_already_exists
| Not_token_owner
| Mismatched_token_permissions
| Overflow
| Signed_command_on_snapp_account
| Snapp_account_not_present
| Update_not_permitted
| Incorrect_nonce
[@@deriving sexp, yojson, equal, compare, enum]
of_transaction
函数签名为:
val of_transaction :
sok_digest:Sok_message.Digest.t
-> source:Frozen_ledger_hash.t
-> target:Frozen_ledger_hash.t
-> init_stack:Pending_coinbase.Stack.t
-> pending_coinbase_stack_state:Pending_coinbase_stack_state.t
-> next_available_token_before:Token_id.t
-> next_available_token_after:Token_id.t
-> snapp_account1:Snapp_account.t option
-> snapp_account2:Snapp_account.t option
-> Transaction.Valid.t Transaction_protocol_state.t
-> Tick.Handler.t
-> t Async.Deferred.t
返回的 t 的类型为:
// pickles.ml
module Statement_with_proof = struct
type ('s, 'max_width, _) t =
(* TODO: use Max local max branching instead of max_width *)
's * ('max_width, 'max_width) Proof.t
end
// proof.ml
type ('dlog_me_only, 'pairing_me_only) t =
('dlog_me_only, 'pairing_me_only) Stable.Latest.t =
{ statement :
( Challenge.Constant.t
, Challenge.Constant.t Scalar_challenge.t
, Tick.Field.t Shifted_value.Type1.t
, Tock.Field.t
, 'dlog_me_only
, Digest.Constant.t
, 'pairing_me_only
, Challenge.Constant.t Scalar_challenge.t Bulletproof_challenge.t
Step_bp_vec.t
, Index.t )
Types.Dlog_based.Statement.Minimal.t
; prev_evals :
(Tick.Field.t, Tick.Field.t array) Dlog_plonk_types.All_evals.t
; proof : Tock.Proof.t
}
[@@deriving compare, sexp, yojson, hash, equal]
// pallas_based_plonk.ml
type t =
( Kimchi.Foundations.Fp.t Kimchi.Foundations.or_infinity
, Kimchi.Foundations.Fq.t )
Kimchi.Protocol.prover_proof
对应着Rust中的ProverProof结构为:
/// The proof that the prover creates from a [ProverIndex] and a `witness`.
#[derive(Clone)]
pub struct ProverProof<G: AffineCurve> {
/// All the polynomial commitments required in the proof
pub commitments: ProverCommitments<G>,
/// batched commitment opening proof
pub proof: OpeningProof<G>,
/// Two evaluations over a number of committed polynomials
// TODO(mimoo): that really should be a type Evals { z: PE, zw: PE }
pub evals: [ProofEvaluations<Vec<ScalarField<G>>>; 2],
/// Required evaluation for [Maller's optimization](https://o1-labs.github.io/mina-book/crypto/plonk/maller_15.html#the-evaluation-of-l)
pub ft_eval1: ScalarField<G>,
/// The public input
pub public: Vec<ScalarField<G>>,
/// The challenges underlying the optional polynomials folded into the proof
pub prev_challenges: Vec<(Vec<ScalarField<G>>, PolyComm<G>)>,
}
constraint_system
system
约束系统的生成从下面开始:
let tag, cache_handle, p, Pickles.Provers.[ base; merge ] =
system ~proof_level ~constraint_constants
其中 proof_level 的类型Genesis_constants.Proof_level.t为:
type t = Full | Check | None [@@deriving bin_io_unversioned, equal]
constraint_constants的类型Genesis_constants.Constraint_constants.t为:
type t =
{ sub_windows_per_window : int
; ledger_depth : int
; work_delay : int
; block_window_duration_ms : int
; transaction_capacity_log_2 : int
; pending_coinbase_depth : int
; coinbase_amount : Currency.Amount.Stable.Latest.t
; supercharged_coinbase_factor : int
; account_creation_fee : Currency.Fee.Stable.Latest.t
; fork : Fork_constants.t option
}
[@@deriving bin_io_unversioned, sexp, equal, compare, yojson]
system 函数主要调用Pickles.compile函数,根据约束条件条件,生成约束系统。
let system ~proof_level ~constraint_constants =
time "Transaction_snark.system" (fun () ->
Pickles.compile ~cache:Cache_dir.cache
(module Statement.With_sok.Checked)
(module Statement.With_sok)
~typ:Statement.With_sok.typ
~branches:(module Nat.N2)
~max_branching:(module Nat.N2)
~name:"transaction-snark"
~constraint_constants:
(Genesis_constants.Constraint_constants.to_snark_keys_header
constraint_constants)
~choices:(fun ~self ->
[ Base.rule ~constraint_constants; Merge.rule ~proof_level self ]))
Base.rule
其中Base.rule函数是关键:
let rule ~constraint_constants : _ Pickles.Inductive_rule.t =
{ identifier = "transaction"
; prevs = []
; main =
(fun [] x ->
Run.run_checked (main ~constraint_constants x) ;
[])
; main_value = (fun [] _ -> [])
}
它的返回值类型Pickles.Inductive_rule.t 为:
(* This type models an "inductive rule". It includes
- the list of previous statements which this one assumes
- the snarky main function
- an unchecked version of the main function which computes the "should verify" flags that
allow predecessor proofs to conditionally fail to verify
*)
type ('prev_vars, 'prev_values, 'widths, 'heights, 'a_var, 'a_value) t =
{ identifier : string
; prevs : ('prev_vars, 'prev_values, 'widths, 'heights) H4.T(Tag).t
; main : 'prev_vars H1.T(Id).t -> 'a_var -> 'prev_vars H1.T(E01(B)).t
; main_value :
'prev_values H1.T(Id).t -> 'a_value -> 'prev_vars H1.T(E01(Bool)).t
}
module T (Statement : T0) (Statement_value : T0) = struct
type nonrec ('prev_vars, 'prev_values, 'widths, 'heights) t =
( 'prev_vars
, 'prev_values
, 'widths
, 'heights
, Statement.t
, Statement_value.t )
t
end
main
(* spec for [main statement]:
constraints pass iff there exists
t : Tagged_transaction.t
such that
- applying [t] to ledger with merkle hash [l1] results in ledger with merkle hash [l2].
- applying [t] to [pc.source] with results in pending coinbase stack [pc.target]
- t has fee excess equal to [fee_excess]
- t has supply increase equal to [supply_increase]
where statement includes
l1 : Frozen_ledger_hash.t,
l2 : Frozen_ledger_hash.t,
fee_excess : Amount.Signed.t,
supply_increase : Amount.t
pc: Pending_coinbase_stack_state.t
*)
let%snarkydef main ~constraint_constants
(statement : Statement.With_sok.Checked.t) =
let%bind () = dummy_constraints () in
let%bind (module Shifted) = Tick.Inner_curve.Checked.Shifted.create () in
let%bind t =
with_label __LOC__
(exists Transaction_union.typ ~request:(As_prover.return Transaction))
in
let%bind pending_coinbase_init =
exists Pending_coinbase.Stack.typ ~request:(As_prover.return Init_stack)
in
let%bind state_body =
exists
(Mina_state.Protocol_state.Body.typ ~constraint_constants)
~request:(As_prover.return State_body)
in
let pc = statement.pending_coinbase_stack_state in
// 计算应用交易后的 root , fee_excess, supply_increase, next_available_token_after变化
let%bind root_after, fee_excess, supply_increase, next_available_token_after
=
apply_tagged_transaction ~constraint_constants
(module Shifted)
statement.source pending_coinbase_init pc.source pc.target
statement.next_available_token_before state_body t
in
let%bind fee_excess =
(* Use the default token for the fee excess if it is zero.
This matches the behaviour of [Fee_excess.rebalance], which allows
[verify_complete_merge] to verify a proof without knowledge of the
particular fee tokens used.
*)
let%bind fee_excess_zero =
Amount.equal_var fee_excess.magnitude Amount.(var_of_t zero)
in
let%map fee_token_l =
Token_id.Checked.if_ fee_excess_zero
~then_:Token_id.(var_of_t default)
~else_:t.payload.common.fee_token
in
{ Fee_excess.fee_token_l
; fee_excess_l = Signed_poly.map ~f:Amount.Checked.to_fee fee_excess
; fee_token_r = Token_id.(var_of_t default)
; fee_excess_r = Fee.Signed.(Checked.constant zero)
}
in
// 验证的约束条件
Checked.all_unit
[ Frozen_ledger_hash.assert_equal root_after statement.target
; Currency.Amount.Checked.assert_equal supply_increase
statement.supply_increase
; Fee_excess.assert_equal_checked fee_excess statement.fee_excess
; Token_id.Checked.Assert.equal next_available_token_after
statement.next_available_token_after
]
main函数保证apply_tagged_transaction
后的状态和statement中一致。
apply_tagged_transaction电路实现如下的功能:
- 更新pending coinbase stack
- 执行交易检查,确定交易状态
- 更新source和receiver的账户(包括fee的计算),如果交易失败,只需要更新fee
- 确保账户更新后的状态树根一致
- 更新fee excess (fee的增量信息)
- 更新next token ID(如果是mint token的操作,next Token ID需要加1)
- 更新total supply的增量(如果是coinbase交易,total supply会增加)
Base的证明电路证明了在source Ledger Hash的基础上执行某个交易,账户状态改变正确。这些状态包括:账户树树根,fee excess,next token ID,total supply增量等等。
其中fee_excess, supply_increase, next_available_token的更新方式如下:
let fee_excess
({ body = { tag; amount; _ }; common = { fee_token; fee; _ } } : t) =
match tag with
| Payment | Stake_delegation | Create_account | Mint_tokens ->
(* For all user commands, the fee excess is just the fee. *)
Fee_excess.of_single (fee_token, Fee.Signed.of_unsigned fee)
| Fee_transfer ->
let excess =
Option.value_exn (Amount.add_fee amount fee)
|> Amount.to_fee |> Fee.Signed.of_unsigned |> Fee.Signed.negate
in
Fee_excess.of_single (fee_token, excess)
| Coinbase ->
Fee_excess.of_single (Token_id.default, Fee.Signed.zero)
let supply_increase (payload : payload) =
let tag = payload.body.tag in
match tag with
| Coinbase ->
payload.body.amount
| Payment | Stake_delegation | Create_account | Mint_tokens | Fee_transfer ->
Amount.zero
let next_available_token (payload : payload) tid =
match payload.body.tag with
| Payment | Stake_delegation | Mint_tokens | Coinbase | Fee_transfer ->
tid
| Create_account when Token_id.(equal invalid) payload.body.token_id ->
(* Creating a new token. *)
Token_id.next tid
| Create_account ->
(* Creating an account for an existing token. *)
tid
Pickles.compile
let compile :
type a_var a_value prev_varss prev_valuess widthss heightss max_branching branches.
?self:(a_var, a_value, max_branching, branches) Tag.t
-> ?cache:Key_cache.Spec.t list
-> ?disk_keys:
(Cache.Step.Key.Verification.t, branches) Vector.t
* Cache.Wrap.Key.Verification.t
-> (module Statement_var_intf with type t = a_var)
-> (module Statement_value_intf with type t = a_value)
-> typ:(a_var, a_value) Impls.Step.Typ.t
-> branches:(module Nat.Intf with type n = branches)
-> max_branching:(module Nat.Add.Intf with type n = max_branching)
-> name:string
-> constraint_constants:Snark_keys_header.Constraint_constants.t
-> choices:
( self:(a_var, a_value, max_branching, branches) Tag.t
-> ( prev_varss
, prev_valuess
, widthss
, heightss
, a_var
, a_value )
H4_2.T(Inductive_rule).t)
-> (a_var, a_value, max_branching, branches) Tag.t
* Cache_handle.t
* (module Proof_intf
with type t = (max_branching, max_branching) Proof.t
and type statement = a_value)
* ( prev_valuess
, widthss
, heightss
, a_value
, (max_branching, max_branching) Proof.t Deferred.t )
H3_2.T(Prover).t =
fun ?self ?(cache = []) ?disk_keys (module A_var) (module A_value) ~typ
~branches ~max_branching ~name ~constraint_constants ~choices ->
let self =
match self with
| None ->
{ Tag.id = Type_equal.Id.create ~name sexp_of_opaque; kind = Compiled }
| Some self ->
self
in
let module M = Make (A_var) (A_value) in
let rec conv_irs :
type v1ss v2ss wss hss.
(v1ss, v2ss, wss, hss, a_var, a_value) H4_2.T(Inductive_rule).t
-> (v1ss, v2ss, wss, hss) H4.T(M.IR).t = function
| [] ->
[]
| r :: rs ->
r :: conv_irs rs
in
let provers, wrap_vk, wrap_disk_key, cache_handle =
M.compile ~self ~cache ?disk_keys ~branches ~max_branching ~name ~typ
~constraint_constants ~choices:(fun ~self -> conv_irs (choices ~self))
in
let (module Max_branching) = max_branching in
let T = Max_branching.eq in
let module P = struct
type statement = A_value.t
module Max_local_max_branching = Max_branching
module Max_branching_vec = Nvector (Max_branching)
include Proof.Make (Max_branching) (Max_local_max_branching)
let id = wrap_disk_key
let verification_key = wrap_vk
let verify ts =
verify
(module Max_branching)
(module A_value)
(Lazy.force verification_key)
ts
let statement (T p : t) = p.statement.pass_through.app_state
end in
(self, cache_handle, (module P), provers)
其中关键调用了:
let provers, wrap_vk, wrap_disk_key, cache_handle =
M.compile ~self ~cache ?disk_keys ~branches ~max_branching ~name ~typ
~constraint_constants ~choices:(fun ~self -> conv_irs (choices ~self))
在M.compile合成约束系统,生成pk 和 vk
let cs = constraint_system ~exposing:[ typ ] main
let ((pk, vk) as res) =
Common.time "step read or generate" (fun () ->
Cache.Step.read_or_generate cache k_p k_v typ main)
constraint_system
let constraint_system (type a s checked k_var) :
run:(a, s, checked) Checked.Runner.run
-> exposing:(checked, _, k_var, _) t
-> k_var
-> R1CS_constraint_system.t =
fun ~run ~exposing k -> r1cs_h ~run (ref 1) exposing k
let r1cs_h : type a s checked r2 k1 k2.
run:(a, s, checked) Checked.Runner.run
-> int ref
-> (checked, r2, k1, k2) t
-> k1
-> R1CS_constraint_system.t =
fun ~run next_input t k ->
let r = collect_input_constraints next_input t k in
let run_in_run r state =
let state, x = Checked.run r state in
run x state
in
Checked.constraint_system ~run:run_in_run ~num_inputs:(!next_input - 1) r
let constraint_system ~run ~num_inputs t : R1CS_constraint_system.t =
let input = field_vec () in
let next_auxiliary = ref (1 + num_inputs) in
let aux = field_vec () in
let system = R1CS_constraint_system.create () in
let state =
Runner.State.make ~num_inputs ~input ~next_auxiliary ~aux ~system None
in1
ignore (run t state) ;
let auxiliary_input_size = !next_auxiliary - (1 + num_inputs) in
R1CS_constraint_system.set_auxiliary_input_size system
auxiliary_input_size ;
system
其中 R1CS_constraint_system定义为:
module R1CS_constraint_system =
Plonk_constraint_system.Make (Field) (Kimchi.Protocol.Gates.Vector.Fq)
(struct
let params =
Sponge.Params.(
map pasta_q_3 ~f:(fun x ->
Field.of_bigint (Bigint256.of_decimal_string x)))
end)
R1CS_constraint_system.t 的类型为:
type ('a, 'f) t =
{ (* map of cells that share the same value (enforced by to the permutation) *)
equivalence_classes : Row.t Position.t list V.Table.t
; (* How to compute each internal variable (as a linear combination of other variables) *)
internal_vars : (('f * V.t) list * 'f option) Internal_var.Table.t
; (* The variables that hold each witness value for each row, in reverse order *)
mutable rows_rev : V.t option array list
; (* a circuit is described by a series of gates. A gate is finalized if TKTK *)
mutable gates :
[ `Finalized | `Unfinalized_rev of (unit, 'f) Gate_spec.t list ]
; (* an instruction pointer *)
mutable next_row : int
; (* hash of the circuit, for distinguishing different circuits *)
mutable hash : Hash_state.t
; (* the size of the public input (which fills the first rows of our constraint system *)
public_input_size : int Core_kernel.Set_once.t
; (* whatever is not public input *)
mutable auxiliary_input_size : int
; (* V.t's corresponding to constant values. We reuse them so we don't need to
use a fresh generic constraint each time to create a constant. *)
cached_constants : ('f, V.t) Core_kernel.Hashtbl.t
(* The [equivalence_classes] field keeps track of the positions which must be
enforced to be equivalent due to the fact that they correspond to the same V.t value.
I.e., positions that are different usages of the same [V.t].
We use a union-find data structure to track equalities that a constraint system wants
enforced *between* [V.t] values. Then, at the end, for all [V.t]s that have been unioned
together, we combine their equivalence classes in the [equivalence_classes] table into
a single equivalence class, so that the permutation argument enforces these desired equalities
as well. *)
; union_finds : V.t Core_kernel.Union_find.t V.Table.t
}
pk and vk
对于pk 和 vk:
module Keypair = struct
type t = {pk: Proving_key.t; vk: Verification_key.t}
[@@deriving fields, bin_io]
let create = Fields.create
let of_backend_keypair kp = {pk= Keypair.pk kp; vk= Keypair.vk kp}
let generate = Fn.compose of_backend_keypair Backend.Keypair.create
end
对应着:
module Keypair = Dlog_plonk_based_keypair.Make (struct
let name = "pallas"
module Rounds = Rounds
module Urs = Kimchi.Protocol.SRS.Fq
module Index = Kimchi.Protocol.Index.Fq // 对应着 pk
module Curve = Curve
module Poly_comm = Fq_poly_comm
module Scalar_field = Field
module Verifier_index = Kimchi.Protocol.VerifierIndex.Fq // 应对着vk
module Gate_vector = Kimchi.Protocol.Gates.Vector.Fq
module Constraint_system = R1CS_constraint_system
end)
Pickles.Provers.[ base; merge ]
let of_transaction_union sok_digest source target ~init_stack
~pending_coinbase_stack_state ~next_available_token_before
~next_available_token_after transaction state_body handler =
let s =
{ Statement.source
; target
; sok_digest
; next_available_token_before
; next_available_token_after
; fee_excess = Transaction_union.fee_excess transaction
; supply_increase = Transaction_union.supply_increase transaction
; pending_coinbase_stack_state
}
in
let%map.Async.Deferred proof =
base []
~handler:
(Base.transaction_union_handler handler transaction state_body
init_stack)
s
in
{ statement = s; proof }
Merge proof
run_ snark_work
| Merge (_, proof1, proof2) ->
process (fun () -> M.merge ~sok_digest proof1 proof2) )
其中 proof1 和 proof2 类型 Transaction_snark.Stable.Latest.t为:
type t =
{ statement : Statement.With_sok.Stable.V1.t; proof : Proof.Stable.V2.t }
[@@deriving compare, equal, fields, sexp, version, yojson, hash]
//Statement.with_sok_Stable.V1.t
type ( 'ledger_hash
, 'amount
, 'pending_coinbase
, 'fee_excess
, 'token_id
, 'sok_digest )
poly =
( 'ledger_hash
, 'amount
, 'pending_coinbase
, 'fee_excess
, 'token_id
, 'sok_digest )
Poly.t =
{ source : 'ledger_hash
; target : 'ledger_hash
; supply_increase : 'amount
; pending_coinbase_stack_state : 'pending_coinbase
; fee_excess : 'fee_excess
; next_available_token_before : 'token_id
; next_available_token_after : 'token_id
; sok_digest : 'sok_digest
}
// Proof.Stable.V2.t
type t = Pickles.Proof.Branching_2.Stable.V2.t
[@@deriving
version { asserted }, yojson, bin_io, compare, equal, sexp, hash]
Merge .rule
let rule ~proof_level self : _ Pickles.Inductive_rule.t =
let prev_should_verify =
match proof_level with
| Genesis_constants.Proof_level.Full ->
true
| _ ->
false
in
let b = Boolean.var_of_value prev_should_verify in
{ identifier = "merge"
; prevs = [ self; self ]
; main =
(fun ps x ->
Run.run_checked (main ps x) ;
[ b; b ])
; main_value = (fun _ _ -> [ prev_should_verify; prev_should_verify ])
}
main
let%snarkydef main
([ s1; s2 ] :
(Statement.With_sok.var * (Statement.With_sok.var * _))
Pickles_types.Hlist.HlistId.t) (s : Statement.With_sok.Checked.t) =
let%bind fee_excess =
Fee_excess.combine_checked s1.Statement.fee_excess s2.Statement.fee_excess
in
let%bind () =
with_label __LOC__
(let%bind valid_pending_coinbase_stack_transition =
Pending_coinbase.Stack.Checked.check_merge
~transition1:
( s1.pending_coinbase_stack_state.source
, s1.pending_coinbase_stack_state.target )
~transition2:
( s2.pending_coinbase_stack_state.source
, s2.pending_coinbase_stack_state.target )
in
Boolean.Assert.is_true valid_pending_coinbase_stack_transition)
in
let%bind supply_increase =
Amount.Checked.add s1.supply_increase s2.supply_increase
in
Checked.all_unit
[ Fee_excess.assert_equal_checked fee_excess s.fee_excess
; Amount.Checked.assert_equal supply_increase s.supply_increase
; Frozen_ledger_hash.assert_equal s.source s1.source
; Frozen_ledger_hash.assert_equal s1.target s2.source
; Frozen_ledger_hash.assert_equal s2.target s.target
; Token_id.Checked.Assert.equal s.next_available_token_before
s1.next_available_token_before
; Token_id.Checked.Assert.equal s1.next_available_token_after
s2.next_available_token_before
; Token_id.Checked.Assert.equal s2.next_available_token_after
s.next_available_token_after
]
交易证明合并的意思是,两个Base证明的状态可以合并。举个例子:
(R0 -> R1, t0, f0, p0s, p0t, nt0, ts0)
存在一个交易t0,使状态根从R0变化到R1,f0 - fee excess, nt0 - next token ID, ts0 - total supply增量,p0s/p0t - pending coinbase的source和target。
(R1 -> R2, t1, f1, p1s, p1t, nt1, ts1)
存在一个交易t1,使状态根从R1变化到R2,f1 - fee excess, nt1 - next token ID, ts1 - total supply增量,p1s/p1t - pending coinbase的source和target。
就可以通过Merge的电路证明,这两笔交易状态连续,“合并”后的状态变化为:
(R0 -> R2, t0t1, f0+f1, p0s, p1t, nt1, ts0+ts1)
总结一下Merge的电路,验证两个s1/s2对应的证明,并验证它们的“逻辑”联系,并输出新的statement,作为合并后的状态
merge
let merge ({ statement = t12; _ } as x12) ({ statement = t23; _ } as x23)
~sok_digest =
if not (Frozen_ledger_hash.( = ) t12.target t23.source) then //验证t12.targe = t23.source
failwithf
!"Transaction_snark.merge: t12.target <> t23.source \
(%{sexp:Frozen_ledger_hash.t} vs %{sexp:Frozen_ledger_hash.t})"
t12.target t23.source () ;
if
not
(Token_id.( = ) t12.next_available_token_after
t23.next_available_token_before)
// 验证 t12.next_available_token_after = t23.next_available_token_before
then
failwithf
!"Transaction_snark.merge: t12.next_available_token_befre <> \
t23.next_available_token_after (%{sexp:Token_id.t} vs \
%{sexp:Token_id.t})"
t12.next_available_token_after t23.next_available_token_before () ;
let open Async.Deferred.Or_error.Let_syntax in
let%bind fee_excess = // 合并fee_excess
Async.return @@ Fee_excess.combine t12.fee_excess t23.fee_excess
and supply_increase = // 合并supply_increase
Amount.add t12.supply_increase t23.supply_increase
|> Option.value_map ~f:Or_error.return
~default:
(Or_error.errorf
"Transaction_snark.merge: Supply change amount overflow")
|> Async.return
in
let s : Statement.With_sok.t =
{ Statement.source = t12.source
; target = t23.target
; supply_increase
; fee_excess
; next_available_token_before = t12.next_available_token_before
; next_available_token_after = t23.next_available_token_after
; pending_coinbase_stack_state =
{ source = t12.pending_coinbase_stack_state.source
; target = t23.pending_coinbase_stack_state.target
}
; sok_digest
}
in
let%map.Async.Deferred proof = // 生成证明
merge [ (x12.statement, x12.proof); (x23.statement, x23.proof) ] s
in
Ok { statement = s; proof }
区块证明
区块证明实现在lib/blockchain_snark/blockchain_snark_state.ml的step函数,证明某个区块的合法性。大体的证明思路如下:
证明存在前面一个世界状态,通过这个状态可以计算出新的Consensus信息。由新的Consensus信息和新的需证明的链的状态信息构建新的世界状态。同时存在一个Transaction证明,证明世界状态从前面一个状态变化到新的构建的世界状态。
(* Blockchain_snark ~old ~nonce ~ledger_snark ~ledger_hash ~timestamp ~new_hash
Input:
old : Blockchain.t
old_snark : proof
nonce : int
work_snark : proof
ledger_hash : Ledger_hash.t
timestamp : Time.t
new_hash : State_hash.t
Witness:
transition : Transition.t
such that
the old_snark verifies against old
new = update_with_asserts(old, nonce, timestamp, ledger_hash)
hash(new) = new_hash
the work_snark verifies against the old.ledger_hash and new_ledger_hash
new.timestamp > old.timestamp
transition consensus data is valid
new consensus state is a function of the old consensus state
*)
let%snarkydef step ~(logger : Logger.t)
~(proof_level : Genesis_constants.Proof_level.t)
~(constraint_constants : Genesis_constants.Constraint_constants.t)
Hlist.HlistId.
[ previous_state_hash
; (txn_snark : Transaction_snark.Statement.With_sok.Checked.t)
] new_state_hash : (_, _) Tick.Checked.t =
let%bind transition =
with_label __LOC__
(exists Snark_transition.typ ~request:(As_prover.return Transition))
in
let%bind previous_state, previous_state_body_hash =
let%bind t =
with_label __LOC__
(exists
(Protocol_state.typ ~constraint_constants)
~request:(As_prover.return Prev_state))
in
let%bind h, body = Protocol_state.hash_checked t in
let%map () =
with_label __LOC__ (State_hash.assert_equal h previous_state_hash)
in
(t, body)
in
let%bind `Success updated_consensus_state, consensus_state =
with_label __LOC__
(Consensus_state_hooks.next_state_checked ~constraint_constants
~prev_state:previous_state ~prev_state_hash:previous_state_hash
transition txn_snark.supply_increase)
in
let supercharge_coinbase =
Consensus.Data.Consensus_state.supercharge_coinbase_var consensus_state
in
let prev_pending_coinbase_root =
previous_state |> Protocol_state.blockchain_state
|> Blockchain_state.staged_ledger_hash
|> Staged_ledger_hash.pending_coinbase_hash_var
in
let%bind genesis_state_hash =
(*get the genesis state hash from previous state unless previous state is the genesis state itslef*)
Protocol_state.genesis_state_hash_checked ~state_hash:previous_state_hash
previous_state
in
let%bind new_state, is_base_case =
let t =
Protocol_state.create_var ~previous_state_hash ~genesis_state_hash
~blockchain_state:(Snark_transition.blockchain_state transition)
~consensus_state
~constants:(Protocol_state.constants previous_state)
in
let%bind is_base_case =
Protocol_state.consensus_state t
|> Consensus.Data.Consensus_state.is_genesis_state_var
in
let%bind previous_state_hash =
match constraint_constants.fork with
| Some { previous_state_hash = fork_prev; _ } ->
State_hash.if_ is_base_case
~then_:(State_hash.var_of_t fork_prev)
~else_:t.previous_state_hash
| None ->
Checked.return t.previous_state_hash
in
let t = { t with previous_state_hash } in
let%map () =
let%bind h, _ = Protocol_state.hash_checked t in
with_label __LOC__ (State_hash.assert_equal h new_state_hash)
in
(t, is_base_case)
in
let%bind txn_snark_should_verify, success =
let%bind ledger_hash_didn't_change =
Frozen_ledger_hash.equal_var
( previous_state |> Protocol_state.blockchain_state
|> Blockchain_state.snarked_ledger_hash )
txn_snark.target
and supply_increase_is_zero =
Currency.Amount.(equal_var txn_snark.supply_increase (var_of_t zero))
in
let%bind new_pending_coinbase_hash, deleted_stack, no_coinbases_popped =
let coinbase_receiver =
Consensus.Data.Consensus_state.coinbase_receiver_var consensus_state
in
let%bind root_after_delete, deleted_stack =
Pending_coinbase.Checked.pop_coinbases ~constraint_constants
prev_pending_coinbase_root
~proof_emitted:(Boolean.not ledger_hash_didn't_change)
in
(*If snarked ledger hash did not change (no new ledger proof) then pop_coinbases should be a no-op*)
let%bind no_coinbases_popped =
Pending_coinbase.Hash.equal_var root_after_delete
prev_pending_coinbase_root
in
(*new stack or update one*)
let%map new_root =
with_label __LOC__
(Pending_coinbase.Checked.add_coinbase ~constraint_constants
root_after_delete
(Snark_transition.pending_coinbase_update transition)
~coinbase_receiver ~supercharge_coinbase previous_state_body_hash)
in
(new_root, deleted_stack, no_coinbases_popped)
in
let pending_coinbase_source_stack =
Pending_coinbase.Stack.Checked.create_with deleted_stack
in
let%bind txn_snark_input_correct =
let lh t =
Protocol_state.blockchain_state t
|> Blockchain_state.snarked_ledger_hash
in
let open Checked in
let%bind () =
Fee_excess.(assert_equal_checked (var_of_t zero) txn_snark.fee_excess)
in
all
[ Frozen_ledger_hash.equal_var txn_snark.source (lh previous_state)
; Frozen_ledger_hash.equal_var txn_snark.target (lh new_state)
; Pending_coinbase.Stack.equal_var
txn_snark.pending_coinbase_stack_state.source
pending_coinbase_source_stack
; Pending_coinbase.Stack.equal_var
txn_snark.pending_coinbase_stack_state.target deleted_stack
; Token_id.Checked.equal txn_snark.next_available_token_before
( previous_state |> Protocol_state.blockchain_state
|> Blockchain_state.snarked_next_available_token )
; Token_id.Checked.equal txn_snark.next_available_token_after
( transition |> Snark_transition.blockchain_state
|> Blockchain_state.snarked_next_available_token )
]
>>= Boolean.all
in
let%bind nothing_changed =
let%bind next_available_token_didn't_change =
Token_id.Checked.equal txn_snark.next_available_token_after
txn_snark.next_available_token_before
in
Boolean.all
[ ledger_hash_didn't_change
; supply_increase_is_zero
; no_coinbases_popped
; next_available_token_didn't_change
]
in
let%bind correct_coinbase_status =
let new_root =
transition |> Snark_transition.blockchain_state
|> Blockchain_state.staged_ledger_hash
|> Staged_ledger_hash.pending_coinbase_hash_var
in
Pending_coinbase.Hash.equal_var new_pending_coinbase_hash new_root
in
let%bind () =
Boolean.Assert.any [ txn_snark_input_correct; nothing_changed ]
in
let transaction_snark_should_verifiy = Boolean.not nothing_changed in
let%bind result =
Boolean.all [ updated_consensus_state; correct_coinbase_status ]
in
let%map () =
as_prover
As_prover.(
Let_syntax.(
let%map txn_snark_input_correct =
read Boolean.typ txn_snark_input_correct
and nothing_changed = read Boolean.typ nothing_changed
and no_coinbases_popped = read Boolean.typ no_coinbases_popped
and updated_consensus_state =
read Boolean.typ updated_consensus_state
and correct_coinbase_status =
read Boolean.typ correct_coinbase_status
and result = read Boolean.typ result in
[%log trace]
"blockchain snark update success: $result = \
(transaction_snark_input_correct=$transaction_snark_input_correct \
∨ nothing_changed \
(no_coinbases_popped=$no_coinbases_popped)=$nothing_changed) \
∧ updated_consensus_state=$updated_consensus_state ∧ \
correct_coinbase_status=$correct_coinbase_status"
~metadata:
[ ( "transaction_snark_input_correct"
, `Bool txn_snark_input_correct )
; ("nothing_changed", `Bool nothing_changed)
; ("updated_consensus_state", `Bool updated_consensus_state)
; ("correct_coinbase_status", `Bool correct_coinbase_status)
; ("result", `Bool result)
; ("no_coinbases_popped", `Bool no_coinbases_popped)
]))
in
(transaction_snark_should_verifiy, result)
in
let txn_snark_should_verify =
match proof_level with
| Check | None ->
Boolean.false_
| Full ->
txn_snark_should_verify
in
let prev_should_verify =
match proof_level with
| Check | None ->
Boolean.false_
| Full ->
Boolean.not is_base_case
in
let%map () = Boolean.Assert.any [ is_base_case; success ] in
(prev_should_verify, txn_snark_should_verify)
Mina 数据结构
参考
https://docs.minaprotocol.com/en
https://github.com/MinaProtocol/mina
https://o1-labs.github.io/ocamlbyexample/basics-opam.html
https://docs.ocaml.pro/about.html
https://www.craigfe.io/operator-lookup/
https://mp.weixin.qq.com/s/sYe7fQSSy6Ix9xIYWMNWfw
https://mp.weixin.qq.com/s/mvh4gHaVDvdwIe157UTLZw
https://zkproof.org/2020/06/08/recursive-snarks/
https://github.com/ChainSafe/mina-rs/tree/main/protocol/test-fixtures/src/data