123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958(*****************************************************************************)(* *)(* Open Source 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. *)(* *)(*****************************************************************************)openError_monadincludeCryptobox_intfmoduleBase58=Tezos_crypto.Base58moduleSrs_g1=Bls12_381_polynomial.Srs.Srs_g1moduleSrs_g2=Bls12_381_polynomial.Srs.Srs_g2typeerror+=Failed_to_load_trusted_setupofstringlet()=register_error_kind`Permanent~id:"dal.node.trusted_setup_loading_failed"~title:"Trusted setup loading failed"~description:"Trusted setup failed to load"~pp:(funppfmsg->Format.fprintfppf"Trusted setup failed to load: %s"msg)Data_encoding.(obj1(req"msg"string))(function|Failed_to_load_trusted_setupparameter->Someparameter|_->None)(funparameter->Failed_to_load_trusted_setupparameter)typeinitialisation_parameters={srs_g1:Srs_g1.t;srs_g2:Srs_g2.t}(* Initialisation parameters are supposed to be instantiated once. *)letinitialisation_parameters=refNonetypeerror+=Dal_initialisation_twice(* This function is expected to be called once. *)letload_parametersparameters=letopenResult_syntaxinmatch!initialisation_parameterswith|None->initialisation_parameters:=Someparameters;return_unit|Some_->fail[Dal_initialisation_twice](* FIXME https://gitlab.com/tezos/tezos/-/issues/3400
An integrity check is run to ensure the validity of the files. *)letinitialisation_parameters_from_files~g1_path~g2_path=letopenLwt_result_syntaxin(* FIXME https://gitlab.com/tezos/tezos/-/issues/3409
The `21` constant is the logarithmic size of the file. Can this
constant be recomputed? Even though it should be determined by
the integrity check. *)letlogarithmic_size=21inletto_bigstringpath=letopenLwt_syntaxinlet*fd=Lwt_unix.openfilepath[Unix.O_RDONLY]0o440inLwt.finalize(fun()->return(Lwt_bytes.map_file~fd:(Lwt_unix.unix_file_descrfd)~shared:false~size:(1lsllogarithmic_size)()))(fun()->Lwt_unix.closefd)inlet*!srs_g1_bigstring=to_bigstringg1_pathinlet*!srs_g2_bigstring=to_bigstringg2_pathinmatchletopenResult_syntaxinlet*srs_g1=Srs_g1.of_bigstringsrs_g1_bigstringinlet*srs_g2=Srs_g2.of_bigstringsrs_g2_bigstringinreturn(srs_g1,srs_g2)with|Error(`End_of_files)->tzfail(Failed_to_load_trusted_setups)|Error(`Invalid_pointp)->tzfail(Failed_to_load_trusted_setup(Printf.sprintf"Invalid point %i"p))|Ok(srs_g1,srs_g2)->return{srs_g1;srs_g2}(* The srs is made of the initialisation_parameters and two
well-choosen points. Building the srs from the initialisation
parameters is almost cost-free. *)typesrs={raw:initialisation_parameters;kate_amortized_srs_g2_shards:Bls12_381.G2.t;kate_amortized_srs_g2_pages:Bls12_381.G2.t;}moduleInner=struct(* Scalars are elements of the prime field Fr from BLS. *)moduleScalar=Bls12_381.FrmodulePolynomials=Bls12_381_polynomial.Polynomial(* Operations on vector of scalars *)moduleEvaluations=Bls12_381_polynomial.Evaluations(* Domains for the Fast Fourier Transform (FTT). *)moduleDomains=Bls12_381_polynomial.Domaintypeslot=bytestypescalar=Scalar.ttypepolynomial=Polynomials.ttypecommitment=Bls12_381.G1.ttypeshard_proof=Bls12_381.G1.ttypecommitment_proof=Bls12_381.G1.ttype_proof_single=Bls12_381.G1.ttypepage_proof=Bls12_381.G1.ttypepage=bytestypeshare=Scalar.tarraytypeshard={index:int;share:share}typeshards_proofs_precomputation=Scalar.tarray*page_proofarrayarraymoduleEncoding=structopenData_encodingletfr_encoding=convBls12_381.Fr.to_bytesBls12_381.Fr.of_bytes_exn(Fixed.bytesBls12_381.Fr.size_in_bytes)(* FIXME https://gitlab.com/tezos/tezos/-/issues/3391
The commitment is not bounded. *)letg1_encoding=convBls12_381.G1.to_compressed_bytesBls12_381.G1.of_compressed_bytes_exnbyteslet_proof_shards_encoding=g1_encodinglet_proof_single_encoding=g1_encodingletpage_proof_encoding=g1_encodingletshare_encoding=arrayfr_encodingletshard_encoding=conv(fun{index;share}->(index,share))(fun(index,share)->{index;share})(tup2int31share_encoding)letshards_proofs_precomputation_encoding=tup2(arrayfr_encoding)(array(arrayg1_encoding))endincludeEncodingmoduleCommitment=structtypet=commitmenttypeBase58.data+=Dataoftletzero=Bls12_381.G1.zeroletequal=Bls12_381.G1.eqletcommitment_to_bytes=Bls12_381.G1.to_compressed_bytesletcommitment_of_bytes_opt=Bls12_381.G1.of_compressed_bytes_optletcommitment_of_bytes_exnbytes=matchBls12_381.G1.of_compressed_bytes_optbyteswith|None->Format.kasprintfStdlib.failwith"Unexpected data (DAL commitment)"|Somecommitment->commitment(* We divide by two because we use the compressed representation. *)letcommitment_size=Bls12_381.G1.size_in_bytes/2letto_stringcommitment=commitment_to_bytescommitment|>Bytes.to_stringletof_string_optstr=commitment_of_bytes_opt(String.to_bytesstr)letb58check_encoding=Base58.register_encoding~prefix:Base58.Prefix.slot_header~length:commitment_size~to_raw:to_string~of_raw:of_string_opt~wrap:(funx->Datax)letraw_encoding=letopenData_encodinginconvcommitment_to_bytescommitment_of_bytes_exn(Fixed.bytescommitment_size)includeTezos_crypto.Helpers.Make(structtypet=commitmentletname="DAL_commitment"lettitle="Commitment representation for the DAL"letb58check_encoding=b58check_encodingletraw_encoding=raw_encodingletcompare=compareletequal=(=)lethash_=(* The commitment is not hashed. This is ensured by the
function exposed. We only need the Base58 encoding and the
rpc_arg. *)assertfalseletseeded_hash__=(* Same argument. *)assertfalseend)endmoduleCommitment_proof=structletzero=Bls12_381.G1.zeroletto_bytes=Bls12_381.G1.to_compressed_bytesletof_bytes_exnbytes=matchBls12_381.G1.of_compressed_bytes_optbyteswith|None->Format.kasprintfStdlib.failwith"Unexpected data (DAL commitment proof)"|Someproof->proof(* We divide by two because we use the compressed representation. *)letsize=Bls12_381.G1.size_in_bytes/2letraw_encoding=letopenData_encodinginconvto_bytesof_bytes_exn(Fixed.bytessize)letencoding=raw_encodingend(* Number of bytes fitting in a Scalar.t. Since scalars are integer modulo
r~2^255, we restrict ourselves to 248-bit integers (31 bytes). *)letscalar_bytes_amount=Scalar.size_in_bytes-1(* Builds group of nth roots of unity, a valid domain for the FFT. *)letmake_domainn=Domains.build_power_of_twoZ.(log2up(of_intn))typet={redundancy_factor:int;slot_size:int;page_size:int;number_of_shards:int;k:int;n:int;(* k and n are the parameters of the erasure code. *)domain_k:Domains.t;(* Domain for the FFT on slots as polynomials to be erasure encoded. *)domain_2k:Domains.t;domain_n:Domains.t;(* Domain for the FFT on erasure encoded slots (as polynomials). *)shard_size:int;(* Length of a shard in terms of scalar elements. *)pages_per_slot:int;(* Number of slot pages. *)page_length:int;remaining_bytes:int;evaluations_log:int;(* Log of the number of evaluations that constitute an erasure encoded
polynomial. *)evaluations_per_proof_log:int;(* Log of the number of evaluations contained in a shard. *)proofs_log:int;(* Log of th number of shards proofs. *)srs:srs;}letensure_validityt=letopenResult_syntaxinletsrs_size=Srs_g1.sizet.srs.raw.srs_g1inletsrs_size_g2=Srs_g2.sizet.srs.raw.srs_g2inletis_pow_of_twox=letlogx=Z.(log2(of_intx))in1lsllogx=xinifnot(is_pow_of_twot.slot_size&&is_pow_of_twot.page_size&&is_pow_of_twot.n)then(* According to the specification the lengths of a slot page are
in MiB *)fail(`Fail"Wrong slot size: expected MiB")elseifnot(t.page_size>=32&&t.page_size<=t.slot_size)then(* The size of a page must be greater than 31 bytes (32 > 31 is the next
power of two), the size in bytes of a scalar element, and less than
[t.slot_size]. *)fail(`Fail"Wrong page size")elseifnot(Z.(log2(of_intt.n))<=32&&is_pow_of_twot.k&&t.n>t.k)then(* n must be at most 2^32, the biggest subgroup of 2^i roots of unity in the
multiplicative group of Fr, because the FFTs operate on such groups. *)fail(`Fail"Wrong computed size for n")elseift.k>srs_sizethen(* the committed polynomials have degree t.k - 1 at most,
so t.k coefficients. *)fail(`Fail(Format.asprintf"SRS on G1 size is too small. Expected more than %d. Got %d"t.ksrs_size))elseift.k>Srs_g2.sizet.srs.raw.srs_g2thenfail(`Fail(Format.asprintf"SRS on G2 size is too small. Expected more than %d. Got %d"t.ksrs_size_g2))elsereturntletslot_as_polynomial_length~slot_size=1lslZ.(log2up(succ(of_intslot_size/of_intscalar_bytes_amount)))typeparameters={redundancy_factor:int;page_size:int;slot_size:int;number_of_shards:int;}letparameters_encoding=letopenData_encodinginconv(fun{redundancy_factor;page_size;slot_size;number_of_shards}->(redundancy_factor,page_size,slot_size,number_of_shards))(fun(redundancy_factor,page_size,slot_size,number_of_shards)->{redundancy_factor;page_size;slot_size;number_of_shards})(obj4(req"redundancy_factor"uint8)(req"page_size"uint16)(req"slot_size"int31)(req"number_of_shards"uint16))letpages_per_slot{slot_size;page_size;_}=slot_size/page_size(* Error cases of this functions are not encapsulated into
`tzresult` for modularity reasons. *)letmake({redundancy_factor;slot_size;page_size;number_of_shards}asparameters)=letopenResult_syntaxinletk=slot_as_polynomial_length~slot_sizeinletn=redundancy_factor*kinletshard_size=n/number_of_shardsinletevaluations_log=Z.(log2(of_intn))inletevaluations_per_proof_log=Z.(log2(of_intshard_size))inletpage_length=Int.divpage_sizescalar_bytes_amount+1inlet*srs=match!initialisation_parameterswith|None->fail(`Fail"Dal_cryptobox.make: DAL was not initialisated.")|Someraw->return{raw;kate_amortized_srs_g2_shards=Srs_g2.getraw.srs_g2(1lslevaluations_per_proof_log);kate_amortized_srs_g2_pages=Srs_g2.getraw.srs_g2(1lslZ.(log2up(of_intpage_length)));}inlett={redundancy_factor;slot_size;page_size;number_of_shards;k;n;domain_k=make_domaink;domain_2k=make_domain(2*k);domain_n=make_domainn;shard_size;pages_per_slot=pages_per_slotparameters;page_length;remaining_bytes=page_sizemodscalar_bytes_amount;evaluations_log;evaluations_per_proof_log;proofs_log=evaluations_log-evaluations_per_proof_log;srs;}inensure_validitytletparameters({redundancy_factor;slot_size;page_size;number_of_shards;_}:t)={redundancy_factor;slot_size;page_size;number_of_shards}letpolynomial_degree=Polynomials.degreeletpolynomial_evaluate=Polynomials.evaluateletfft_muldps=letopenEvaluationsinletevaluations=List.map(evaluation_fftd)psininterpolation_fftd(mul_c~evaluations())(* We encode by pages of [page_size] bytes each. The pages
are arranged in cosets to evaluate in batch with Kate
amortized. *)letpolynomial_from_bytes'(t:t)slot=ifBytes.lengthslot<>t.slot_sizethenError(`Slot_wrong_size(Printf.sprintf"message must be %d bytes long"t.slot_size))elseletoffset=ref0inletres=Array.initt.k(fun_->Scalar.(copyzero))inforpage=0tot.pages_per_slot-1doforelt=0tot.page_length-1do(* [!offset >= t.slot_size] because we don't want to read past
the buffer [slot] bounds. *)if!offset>=t.slot_sizethen()elseifelt=t.page_length-1then(letdst=Bytes.createt.remaining_bytesinBytes.blitslot!offsetdst0t.remaining_bytes;offset:=!offset+t.remaining_bytes;res.((elt*t.pages_per_slot)+page)<-Scalar.of_bytes_exndst)elseletdst=Bytes.createscalar_bytes_amountinBytes.blitslot!offsetdst0scalar_bytes_amount;offset:=!offset+scalar_bytes_amount;res.((elt*t.pages_per_slot)+page)<-Scalar.of_bytes_exndstdonedone;Okresletpolynomial_from_slottslot=letopenResult_syntaxinlet*data=polynomial_from_bytes'tslotinOk(Evaluations.interpolation_fft2t.domain_kdata)leteval_cosettevalslotoffsetpage=forelt=0tot.page_length-1doletidx=(elt*t.pages_per_slot)+pageinletcoeff=Scalar.to_bytes(Array.getevalidx)inifelt=t.page_length-1then(Bytes.blitcoeff0slot!offsett.remaining_bytes;offset:=!offset+t.remaining_bytes)else(Bytes.blitcoeff0slot!offsetscalar_bytes_amount;offset:=!offset+scalar_bytes_amount)done(* The pages are arranged in cosets to evaluate in batch with Kate
amortized. *)letpolynomial_to_bytestp=leteval=Evaluations.evaluation_fftt.domain_kp|>Evaluations.to_arrayinletslot=Bytes.initt.slot_size(fun_->'0')inletoffset=ref0inforpage=0tot.pages_per_slot-1doeval_cosettevalslotoffsetpagedone;slotletencodetp=Evaluations.evaluation_fftt.domain_np|>Evaluations.to_array(* The shards are arranged in cosets to evaluate in batch with Kate
amortized. *)letshards_from_polynomialtp=letcodeword=encodetpinletlen_shard=t.n/t.number_of_shardsinletrecloopindexseq=ifindex=t.number_of_shardsthenseqelseletshare=Array.initlen_shard(fun_->Scalar.(copyzero))inforj=0tolen_shard-1doshare.(j)<-codeword.((t.number_of_shards*j)+index)done;loop(index+1)(Seq.cons{index;share}seq)inloop0Seq.empty(* Computes the polynomial N(X) := \sum_{i=0}^{k-1} n_i x_i^{-1} X^{z_i}. *)letcompute_nteval_a'shards=letw=Domains.gett.domain_n1inletn_poly=Array.initt.n(fun_->Scalar.(copyzero))inSeq.iter(fun{index;share}->forj=0toArray.lengthshare-1doletc_i=share.(j)inletz_i=(t.number_of_shards*j)+indexinletx_i=Scalar.poww(Z.of_intz_i)inlettmp=Evaluations.geteval_a'z_iinScalar.mul_inplacetmptmpx_i;(* The call below never fails by construction, so we don't
catch exceptions *)Scalar.inverse_exn_inplacetmptmp;Scalar.mul_inplacetmptmpc_i;n_poly.(z_i)<-tmpdone)shards;n_polyletencoded_share_sizet=(* FIXME: https://gitlab.com/tezos/tezos/-/issues/4289
Improve shard size computation *)letshare_scalar_len=t.n/t.number_of_shardsin(share_scalar_len*Scalar.size_in_bytes)+4letpolynomial_from_shardstshards=ift.k>Seq.lengthshards*t.shard_sizethenError(`Not_enough_shards(Printf.sprintf"there must be at least %d shards to decode"(t.k/t.shard_size)))else(* 1. Computing A(x) = prod_{i=0}^{k-1} (x - w^{z_i}).
Let w be a primitive nth root of unity and
Ω_0 = {w^{number_of_shards j}}_{j=0 to (n/number_of_shards)-1}
be the (n/number_of_shards)-th roots of unity and Ω_i = w^i Ω_0.
Together, the Ω_i's form a partition of the subgroup of the n-th roots
of unity: 𝕌_n = disjoint union_{i ∈ {0, ..., number_of_shards-1}} Ω_i.
Let Z_j := Prod_{w ∈ Ω_j} (x − w). For a random set of shards
S⊆{0, ..., number_of_shards-1} of length k/shard_size, we reorganize the
product A(x) = Prod_{i=0}^{k-1} (x − w^{z_i}) into
A(x) = Prod_{j ∈ S} Z_j.
Moreover, Z_0 = x^|Ω_0| - 1 since x^|Ω_0| - 1 contains all roots of Z_0
and conversely. Multiplying each term of the polynomial by the root w^j
entails Z_j = x^|Ω_0| − w^{j*|Ω_0|}.
The intermediate products Z_j have a lower Hamming weight (=2) than
when using other ways of grouping the z_i's into shards.
This also reduces the depth of the recursion tree of the poly_mul
function from log(k) to log(number_of_shards), so that the decoding time
reduces from O(k*log^2(k) + n*log(n)) to O(n*log(n)). *)letmulacci=Polynomials.mul_xnacct.shard_size(Scalar.negate(Domains.gett.domain_n(i*t.shard_size)))inletpartition_productsseq=Seq.fold_left(fun(l,r){index;_}->(mulrindex,l))(Polynomials.one,Polynomials.one)seqinletshards=(* We always consider the first k codeword vector components. *)Seq.take(t.k/t.shard_size)shardsinletp1,p2=partition_productsshardsinleta_poly=fft_mult.domain_2k[p1;p2]in(* 2. Computing formal derivative of A(x). *)leta'=Polynomials.derivativea_polyin(* 3. Computing A'(w^i) = A_i(w^i). *)leteval_a'=Evaluations.evaluation_fftt.domain_na'in(* 4. Computing N(x). *)letn_poly=compute_nteval_a'shardsin(* 5. Computing B(x). *)letb=Evaluations.interpolation_fft2t.domain_nn_polyinletb=Polynomials.copy~len:t.kbinPolynomials.mul_by_scalar_inplaceb(Scalar.of_intt.n)b;(* 6. Computing Lagrange interpolation polynomial P(x). *)letp=fft_mult.domain_2k[a_poly;b]inletp=Polynomials.copy~len:t.kpinPolynomials.opposite_inplacep;Okpletcommittp=Srs_g1.pippengert.srs.raw.srs_g1p(* p(X) of degree n. Max degree that can be committed: d, which is also the
SRS's length - 1. We take d = t.k - 1 since we don't want to commit
polynomials with degree greater than polynomials to be erasure-encoded.
We consider the bilinear groups (G_1, G_2, G_T) with G_1=<g> and G_2=<h>.
- Commit (p X^{d-n}) such that deg (p X^{d-n}) = d the max degree
that can be committed
- Verify: checks if e(commit(p), commit(X^{d-n})) = e(commit(p X^{d-n}), h)
using the commitments for p and p X^{d-n}, and computing the commitment for
X^{d-n} on G_2. *)(* Proves that degree(p) < t.k *)(* FIXME https://gitlab.com/tezos/tezos/-/issues/4192
Generalize this function to pass the slot_size in parameter. *)letprove_commitment(t:t)p=letmax_allowed_committed_poly_degree=t.k-1inletmax_committable_degree=Srs_g1.sizet.srs.raw.srs_g1-1inletoffset_monomial_degree=max_committable_degree-max_allowed_committed_poly_degreein(* Note: this reallocates a buffer of size (Srs_g1.size t.srs.raw.srs_g1)
(2^21 elements in practice), so roughly 100MB. We can get rid of the
allocation by giving an offset for the SRS in Pippenger. *)letp_with_offset=Polynomials.mul_xnpoffset_monomial_degreeScalar.(copyzero)in(* proof = commit(p X^offset_monomial_degree), with deg p < t.k *)committp_with_offset(* Verifies that the degree of the committed polynomial is < t.k *)letverify_commitment(t:t)cmproof=letmax_allowed_committed_poly_degree=t.k-1inletmax_committable_degree=Srs_g1.sizet.srs.raw.srs_g1-1inletoffset_monomial_degree=max_committable_degree-max_allowed_committed_poly_degreeinletcommitted_offset_monomial=(* This [get] cannot raise since
[offset_monomial_degree <= t.k <= Srs_g2.size t.srs.raw.srs_g2]. *)Srs_g2.gett.srs.raw.srs_g2offset_monomial_degreeinletopenBls12_381in(* checking that cm * committed_offset_monomial = proof *)Pairing.pairing_check[(cm,committed_offset_monomial);(proof,G2.(negate(copyone)))]letinversedomain=letn=Array.lengthdomaininArray.initn(funi->ifi=0thenBls12_381.Fr.(copyone)elseArray.getdomain(n-i))letdiff_next_power_of_twox=letlogx=Z.log2(Z.of_intx)inif1lsllogx=xthen0else(1lsl(logx+1))-xletis_pow_of_twox=letlogx=Z.log2(Z.of_intx)in1lsllogx=x(* Implementation of fast amortized Kate proofs
https://github.com/khovratovich/Kate/blob/master/Kate_amortized.pdf). *)(* Precompute first part of Toeplitz trick, which doesn't depends on the
polynomial’s coefficients. *)letpreprocess_multi_reveals~chunk_len~degreesrs=letopenBls12_381inletl=1lslchunk_leninletk=letratio=degree/linletlog_inf=Z.log2(Z.of_intratio)inif1lsllog_inf<ratiothenlog_infelselog_inf+1inletdomain=Domains.build_power_of_twok|>Domains.inverse|>inverseinletprecompute_srsjj=letquotient=(degree-j)/linletpadding=diff_next_power_of_two(2*quotient)inletpoints=Array.init((2*quotient)+padding)(funi->ifi<quotientthenG1.copy(Srs_g1.getsrs(degree-j-((i+1)*l)))elseG1.(copyzero))inG1.fft_inplace~domain~points;pointsin(domain,Array.initlprecompute_srsj)(** Generate proofs of part 3.2.
n, r are powers of two, m = 2^(log2(n)-1)
coefs are f polynomial’s coefficients [f₀, f₁, f₂, …, fm-1]
domain2m is the set of 2m-th roots of unity, used for Toeplitz computation
(domain2m, precomputed_srs_part) = preprocess_multi_reveals r n m srs1
*)letmultiple_multi_reveals~chunk_len~chunk_count~degree~preprocess:(domain2m,precomputed_srs_part)coefs=letopenBls12_381inletn=chunk_len+chunk_countinassert(2<=chunk_len);assert(chunk_len<n);assert(is_pow_of_twodegree);assert(1lslchunk_len<degree);assert(degree<=1lsln);letl=1lslchunk_lenin(* We don’t need the first coefficient f₀. *)letcompute_h_jj=letrest=(degree-j)modlinletquotient=(degree-j)/lin(* Padding in case quotient is not a power of 2 to get proper fft in
Toeplitz matrix part. *)letpadding=diff_next_power_of_two(2*quotient)in(* fm, 0, …, 0, f₁, f₂, …, fm-1 *)letpoints=Array.init((2*quotient)+padding)(funi->ifi<=quotient+(padding/2)thenScalar.(copyzero)elseScalar.copycoefs.(rest+((i-(quotient+padding))*l)))inifj<>0thenpoints.(0)<-Scalar.copycoefs.(degree-j);Scalar.fft_inplace~domain:domain2m~points;Array.map2G1.mulprecomputed_srs_part.(j)pointsinletsum=compute_h_j0inletrecsum_hjj=ifj=lthen()elselethj=compute_h_jjin(* sum.(i) <- sum.(i) + hj.(i) *)Array.iteri(funihij->sum.(i)<-G1.addsum.(i)hij)hj;sum_hj(j+1)insum_hj1;(* Toeplitz matrix-vector multiplication *)G1.ifft_inplace~domain:(inversedomain2m)~points:sum;lethl=Array.subsum0(Array.lengthdomain2m/2)inletphidomain=Domains.build_power_of_twochunk_countinletphidomain=inverse(Domains.inversephidomain)in(* Kate amortized FFT *)G1.fft~domain:phidomain~points:hl(* h = polynomial such that h(y×domain[i]) = zi. *)letinterpolation_h_polyydomainz_list=Scalar.ifft_inplace~domain:(Domains.inversedomain)~points:z_list;letinv_y=Scalar.inverse_exnyinArray.fold_left_map(funinv_yih->Scalar.(mulinv_yiinv_y,mulhinv_yi))Scalar.(copyone)z_list|>snd|>Polynomials.of_dense(* Part 3.2 verifier : verifies that f(w×domain.(i)) = evaluations.(i). *)letverifytcm_fsrs_pointdomain(w,evaluations)proof=letopenBls12_381inleth=interpolation_h_polywdomainevaluationsinletcm_h=committhinletl=Domains.lengthdomaininletsl_min_yl=G2.(addsrs_point(negate(mul(copyone)(Scalar.poww(Z.of_intl)))))inletdiff_commits=G1.(addcm_h(negatecm_f))inPairing.pairing_check[(diff_commits,G2.(copyone));(proof,sl_min_yl)]letprecompute_shards_proofst=preprocess_multi_reveals~chunk_len:t.evaluations_per_proof_log~degree:t.kt.srs.raw.srs_g1let_save_precompute_shards_proofs(preprocess:shards_proofs_precomputation)filename=letchan=open_out_binfilenameinoutput_byteschan(Data_encoding.Binary.to_bytes_exnEncoding.shards_proofs_precomputation_encodingpreprocess);close_out_noerrchanlet_load_precompute_shards_proofsfilename=letchan=open_in_binfilenameinletlen=Int64.to_int(LargeFile.in_channel_lengthchan)inletdata=Bytes.createleninlet()=tryreally_inputchandata0lenwithEnd_of_file->()inletprecomp=Data_encoding.Binary.of_bytes_exnEncoding.shards_proofs_precomputation_encodingdatainclose_in_noerrchan;precompletprove_shardstp=letpreprocess=precompute_shards_proofstinmultiple_multi_reveals~chunk_len:t.evaluations_per_proof_log~chunk_count:t.proofs_log~degree:t.k~preprocess(Polynomials.to_dense_coefficientsp)letverify_shardtcm{index=shard_index;share=shard_evaluations}proof=letd_n=Domains.build_power_of_twot.evaluations_loginletdomain=Domains.build_power_of_twot.evaluations_per_proof_loginverifytcmt.srs.kate_amortized_srs_g2_shardsdomain(Domains.getd_nshard_index,shard_evaluations)prooflet_prove_singletpz=letq,_=Polynomials.(division_xn(p-constant(evaluatepz))1(Scalar.negatez))incommittqlet_verify_singletcm~point~evaluationproof=leth_secret=Srs_g2.gett.srs.raw.srs_g21inBls12_381.(Pairing.pairing_check[(G1.(addcm(negate(mul(copyone)evaluation))),G2.(negate(copyone)));(proof,G2.(addh_secret(negate(mul(copyone)point))));])letprove_pagetppage_index=ifpage_index<0||page_index>=t.pages_per_slotthenError`Segment_index_out_of_rangeelseletl=1lslZ.(log2up(of_intt.page_length))inletwi=Domains.gett.domain_kpage_indexinletquotient,_=Polynomials.(division_xnplScalar.(negate(powwi(Z.of_intl))))inOk(committquotient)(* Parses the [slot_page] to get the evaluations that it contains. The
evaluation points are given by the [slot_page_index]. *)letverify_pagetcm~page_indexpageproof=ifpage_index<0||page_index>=t.pages_per_slotthenError`Segment_index_out_of_rangeelseletexpected_page_length=t.page_sizeinletgot_page_length=Bytes.lengthpageinifexpected_page_length<>got_page_lengththenError`Page_length_mismatchelseletdomain=Domains.build_power_of_twoZ.(log2up(of_intt.page_length))inletslot_page_evaluations=Array.init(1lslZ.(log2up(of_intt.page_length)))(function|iwheni<t.page_length-1->letdst=Bytes.createscalar_bytes_amountinBytes.blitpage(i*scalar_bytes_amount)dst0scalar_bytes_amount;Scalar.of_bytes_exndst|iwheni=t.page_length-1->letdst=Bytes.createt.remaining_bytesinBytes.blitpage(i*scalar_bytes_amount)dst0t.remaining_bytes;Scalar.of_bytes_exndst|_->Scalar.(copyzero))inOk(verifytcmt.srs.kate_amortized_srs_g2_pagesdomain(Domains.gett.domain_kpage_index,slot_page_evaluations)proof)endincludeInnermoduleVerifier=InnermoduleInternal_for_tests=structletinitialisation_parameters_from_slot_size~slot_size=letsize=slot_as_polynomial_length~slot_sizeinletsecret=Bls12_381.Fr.of_string"20812168509434597367146703229805575690060615791308155437936410982393987532344"inletsrs_g1=Srs_g1.generate_insecure(size+1)secretinletsrs_g2=Srs_g2.generate_insecure(size+1)secretin{srs_g1;srs_g2}letload_parametersparameters=initialisation_parameters:=SomeparametersendmoduleConfig=structtypet={activated:bool;srs_size:intoption}letencoding:tData_encoding.t=letopenData_encodinginconv(fun{activated;srs_size}->(activated,srs_size))(fun(activated,srs_size)->{activated;srs_size})(obj2(req"activated"bool)(req"srs_size"(optionint31)))letdefault={activated=false;srs_size=None}letinit_dal~find_srs_filesdal_config=letopenLwt_result_syntaxinifdal_config.activatedthenlet*initialisation_parameters=matchdal_config.srs_sizewith|None->let*?g1_path,g2_path=find_srs_files()ininitialisation_parameters_from_files~g1_path~g2_path|Someslot_size->return(Internal_for_tests.initialisation_parameters_from_slot_size~slot_size)inLwt.return(load_parametersinitialisation_parameters)elsereturn_unitend