123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381(*****************************************************************************)(* *)(* MIT License *)(* Copyright (c) 2022 Nomadic Labs <contact@nomadic-labs.com> *)(* *)(* Permission is hereby granted, free of charge, to any person obtaining a *)(* copy of this software and associated documentation files (the "Software"),*)(* to deal in the Software without restriction, including without limitation *)(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *)(* and/or sell copies of the Software, and to permit persons to whom the *)(* Software is furnished to do so, subject to the following conditions: *)(* *)(* The above copyright notice and this permission notice shall be included *)(* in all copies or substantial portions of the Software. *)(* *)(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *)(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *)(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *)(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *)(* DEALINGS IN THE SOFTWARE. *)(* *)(*****************************************************************************)openLang_coreopenLang_stdlibmoduletypePARAMETERS=sigvalvariant:Variants.tvalwidth:intvalnb_full_rounds:intvalnb_partial_rounds:intvalnb_skips:intvalmds_matrix:stringarrayarrayvalround_constants:stringarrayvalpartial_round_idx_to_permute:intendmoduleMake(PP:PARAMETERS)(L:LIB)=structopenPPopenLmodulePoly=Polynomial.MakeUnivariate(S)typepoly=Poly.t(* Map between scalar variables and an identifier used as degree in their
polynomial representation. Identifiers are guarateed to be unique. *)moduleMap:sigvalinit:unit->unitvaldegree_of_wire:scalarrepr->intvalwire_of_degree:int->scalarreprend=structletassoc:((scalarrepr*int)list*int)ref=ref([],1)letinit()=assoc:=([],1)letdegree_of_wirew=List.find_opt(fun(w',_d)->eqww')(fst!assoc)|>function|Some(_,d)->d|None->letl,d=!associnletl=(w,d)::linassoc:=(l,d+1);dletwire_of_degreed=List.find_opt(fun(_w,d')->d=d')(fst!assoc)|>function|Some(w,_)->w|None->assertfalseendtypepolys=polyarrayletinitial_state=Array.initwidth(fun_->Poly.zero)letprint_statestate=Array.iter(funp->Format.printf"%s\n"(Poly.to_stringp))state;Format.printf"\n"letmds_matrix=mds_matrix|>Array.map(Array.mapS.of_string)letround_constants=round_constants|>Array.mapS.of_string(* Linear composition trick:
- Core idea: The composition of two linear functions is a linear function.
- Application to Poseidon:
Partial rounds are linear functions for (width-1) elements of the state.
We can compose two or more partial rounds (configured with [nb_skips])
before calculating (creating wires for) the actual value of the
elements that are linearly afected.
- Execution:
We store pending linear computations as polynomials, whose monomials
represent the wires that need to be linearly combined.
The i-th wire is represented by monomial x^{i+1}.
The independent term x^{0} is reserved for the constant ~qc if any. *)(* A polynomial represents a linear combination of wires (and a constant).
The i-th wire is associated to monomial x^{i+1} so that wire 0 is not in
conflict with the constant (which is stored in the independent term) *)letpoly_of_wirew=Poly.of_coefficients[(S.one,Map.degree_of_wirew)]letwire_of_polyp=with_label~label:"Poseidon.wire_of_poly"@@letcoeffs=Poly.get_list_coefficientsp|>List.revinletqc,ws=matchcoeffswith(qc,0)::l->(qc,l)|l->(S.zero,l)inNum.add_n~qc@@List.map(fun(q,d)->(q,Map.wire_of_degreed))wslets_boxp:polyt=with_label~label:"Poseidon.s_box"@@ifPoly.is_constantpthenretPoly.(p*p*p*p*p)elseletopenNuminlet*x=wire_of_polypinlet*x5=pow5xinret@@poly_of_wirex5letrecrepeat:n:int->('a->'at)->'a->'at=fun~nfe->ifn<=0thenreteelselet*x=feinrepeat~n:(n-1)fxletstate_map:polys->(poly->polyt)->polyst=funstatef->with_label~label:"Poseidon.state_map"@@letrecaux:polys->int->polyst=funstatej->ifj=widththenretstateelselet*p=fstate.(j)instate.(j)<-p;auxstate(j+1)inauxstate0(* Simplify the state into single-monomial polynomials by evaluating
pending the linear combination that they store *)letcheckpoint:polys->polyst=funstate->with_label~label:"Poseidon.checkpoint"@@letfp=ifPoly.is_constantpthenretpelselet*w=wire_of_polypinret@@poly_of_wirewinstate_mapstatefletapply_matrixstate=letx=Array.copystateinforj=0towidth-1dostate.(j)<-Poly.zero;fori=0towidth-1dostate.(j)<-Poly.(state.(j)+mult_by_scalarmds_matrix.(j).(i)x.(i))donedone;stateletapply_round_key(state,i_round_key)=fori=0towidth-1dostate.(i)<-Poly.addstate.(i)(Poly.constants@@round_constants.(i_round_key+i))done;(state,i_round_key+width)letfull_round:?skip_ark:bool->polys*int->(polys*int)t=fun?(skip_ark=false)(state,i_round_key)->with_label~label:"Poseidon.full_round"@@let*state=state_mapstates_boxinletstate=apply_matrixstateinifskip_arkthenret(state,i_round_key)elseret@@apply_round_key(state,i_round_key)letfull_round128:?skip_ark:bool->polys*int->(polys*int)t=fun?(skip_ark=false)(state,i_round_key)->assert(width=3);with_label~label:"Poseidon.full_round128"@@let*state=checkpointstateinlet*x0=wire_of_polystate.(0)inlet*x1=wire_of_polystate.(1)inlet*x2=wire_of_polystate.(2)inletk=[|S.zero;S.zero;S.zero|]inifnotskip_arkthenfori=0towidth-1dok.(i)<-round_constants.(i_round_key+i)done;let*output=Poseidon.poseidon128_full_round~matrix:mds_matrix~k~variant(x0,x1,x2)in(matchof_listoutputwith|[y0;y1;y2]->state.(0)<-poly_of_wirey0;state.(1)<-poly_of_wirey1;state.(2)<-poly_of_wirey2|_->assertfalse);ret@@(state,i_round_key+width)letpartial_round:batch:int->polys*int->(polys*int)t=fun~batch(state,i_round_key)->with_label~label:"Poseidon.partial_round"@@letf(state,i_round_key)=let*p=s_boxstate.(partial_round_idx_to_permute)instate.(partial_round_idx_to_permute)<-p;letstate=apply_matrixstateinret@@apply_round_key(state,i_round_key)inlet*state,i_round_key=repeat~n:batchf(state,i_round_key)inlet*state=checkpointstateinret(state,i_round_key)letpartial_round128:batch:int->polys*int->(polys*int)t=fun~batch(state,i_round_key)->assert(width=3);assert(batch=4);with_label~label:"Poseidon.partial_round128"@@let*state=checkpointstateinlet*x0=wire_of_polystate.(0)inlet*x1=wire_of_polystate.(1)inlet*x2=wire_of_polystate.(2)inletks=Array.make_matrixwidthbatchS.zeroinforj=0tobatch-1dofori=0towidth-1doks.(i).(j)<-round_constants.(i_round_key+(width*j)+i)donedone;let*output=Poseidon.poseidon128_four_partial_rounds~matrix:mds_matrix~ks~variant(x0,x1,x2)in(matchof_listoutputwith|[y0;y1;y2]->state.(0)<-poly_of_wirey0;state.(1)<-poly_of_wirey1;state.(2)<-poly_of_wirey2|_->assertfalse);ret@@(state,i_round_key+(batch*width))letapply_permutation(state,_)=with_label~label:"Poseidon.apply_permutation"@@letbatch=nb_skips+1inletfull=ifwidth=3thenfull_round128elsefull_roundinletpartial=ifwidth=3&&batch=4thenpartial_round128elsepartial_roundinletstate,i_round_key=apply_round_key(state,0)inlet*state=repeat~n:(nb_full_rounds/2)full(state,i_round_key)inlet*state=repeat~n:(nb_partial_rounds/batch)(partial~batch)stateinlet*state=partial_round~batch:(nb_partial_roundsmodbatch)stateinlet*state=repeat~n:((nb_full_rounds/2)-1)fullstateinfull~skip_ark:truestateletprepare_blockwith_padding(state,blocks_read)rnb_chunksinputs=letblock_size=ifblocks_read<nb_chunks-1thenwidth-1elseifwith_paddingthenrelseArray.lengthinputs-(blocks_read*(width-1))inletoffset=blocks_read*(width-1)inforj=0toblock_size-1doletp=Poly.addstate.(j+1)@@poly_of_wireinputs.(offset+j)instate.(j+1)<-pdone;ifblocks_read=nb_chunks-1&&with_paddingthenstate.(r+1)<-Poly.addstate.(r+1)Poly.one;(state,blocks_read+1)letdigest:?input_length:int->scalarlistrepr->scalarreprt=fun?input_lengthinputs->Map.init();letinputs=Array.of_list@@of_listinputsinletl=Array.lengthinputsinletassert_lengthexpected=leterror_msg=Format.sprintf"digest expects data of length %d, %d given"expectedlinifl<>expectedthenraise@@Invalid_argumenterror_msginOption.iterassert_lengthinput_length;letwith_padding=Option.is_noneinput_lengthinletpolys=Array.initwidth(fun_->Poly.zero)inletnb_blocks=((l-ifwith_paddingthen0else1)/(width-1))+1inletr=lmod(width-1)inwith_label~label:"Poseidon.digest"@@letblock_iteration(state,blocks_read,i_round_key)=letstate,blocks_read=prepare_blockwith_padding(state,blocks_read)rnb_blocksinputsinlet*state,i_round_key=apply_permutation(state,i_round_key)inret(state,blocks_read,i_round_key)inlet*state,_,_=repeat~n:nb_blocksblock_iteration(polys,0,0)inwire_of_polystate.(1)endmodulePoseidon128=structmoduleP:Hash_sig.P_HASH=structmoduleH=Mec.Hash.Poseidon128.Make(S)includeH.Hashletdirect?input_lengthinputs=letctx=init?input_length()inletctx=digestctxinputsingetctxendmoduleV:Hash_sig.HASH=Make(structletvariant=Variants.P128letwidth=3letnb_full_rounds=8letnb_partial_rounds=56letnb_skips=3letmds_matrix=Mds_128.vletround_constants=Ark_128.vletpartial_round_idx_to_permute=2end)endmodulePoseidon252=structmoduleP:Hash_sig.P_HASH=structmoduleH=Mec.Hash.Poseidon252.Make(S)includeH.Hashletdirect?input_lengthinputs=letctx=init?input_length()inletctx=digestctxinputsingetctxendmoduleV:Hash_sig.HASH=Make(structletvariant=Variants.P252letwidth=5letnb_full_rounds=8letnb_partial_rounds=59letnb_skips=4letmds_matrix=Mds_252.vletround_constants=Ark_252.vletpartial_round_idx_to_permute=4end)endmodulePoseidonFull=structmoduleP:Hash_sig.P_HASH=structmoduleH=Mec.Hash.Neptunus.Make(S)includeH.Hashletdirect?input_lengthinputs=letctx=init?input_length()inletctx=digestctxinputsingetctxendmoduleV:Hash_sig.HASH=Make(structletvariant=Variants.PFull128letwidth=3letnb_full_rounds=60letnb_partial_rounds=0letnb_skips=0letmds_matrix=Mds_full.vletround_constants=Ark_full.vletpartial_round_idx_to_permute=0end)end