123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279(*****************************************************************************)(* *)(* 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. *)(* *)(*****************************************************************************)moduleMake(Curve:Mec.CurveSig.AffineEdwardsT)(H:sigmoduleP:Hash_sig.P_HASHmoduleV:Hash_sig.HASHend)=structmoduleCurve=CurveopenLang_core(* vanilla implementation of Schnorr signature using ec-jubjub
* general idea based on
* https://github.com/dusk-network/schnorr/blob/main/src/key_variants/single_key.rs *)moduleP:sigtypepk=Curve.ttypesignature={sig_u_bytes:boollist;sig_r:Curve.t;c_bytes:boollist;}typesk=Curve.Scalar.tvalneuterize:sk->pkvalsign:?compressed:bool->sk->S.t->Curve.Scalar.t->signaturevalverify:?compressed:bool->msg:S.t->pk:pk->signature:signature->unit->boolend=structmoduleH=H.P(* S.t and Curve.t are the same but Curve.t is abstract *)letof_bls_scalars=S.of_z(Curve.Base.to_zs)typesk=Curve.Scalar.ttypepk=Curve.ttypesignature={sig_u_bytes:boollist;sig_r:Curve.t;c_bytes:boollist;}letneuterizesk=Curve.mulCurve.oneskletbls_scalar_to_curve_scalars=Curve.Scalar.of_z(S.to_zs)lethash_fulla=letctx=H.init()inletctx=H.digestctxainH.getctxletsign?(compressed=false)skmsgrand=letr=Curve.mulCurve.onerandinletr_u=Curve.get_u_coordinater|>of_bls_scalarinletr_v=Curve.get_v_coordinater|>of_bls_scalarinletc=ifcompressedthenH.direct~input_length:2[|r_u;msg|]elsehash_full[|r_u;r_v;msg|]inletu=Curve.Scalar.subrand(Curve.Scalar.mul(bls_scalar_to_curve_scalarc)sk)inletnb_bits_base=Z.numbitsS.orderinletsig_u_bytes=Utils.bool_list_of_z~nb_bits:nb_bits_base(Curve.Scalar.to_zu)inletc_bytes=Utils.bool_list_of_z~nb_bits:nb_bits_base(S.to_zc)in{sig_u_bytes;sig_r=r;c_bytes}letverify?(compressed=false)~msg~pk~signature()=letsig_u=Curve.Scalar.of_z@@Utils.bool_list_to_zsignature.sig_u_bytesinletsig_c=Utils.bool_list_to_scalarsignature.c_bytesinletsig_r_u=Curve.get_u_coordinatesignature.sig_r|>of_bls_scalarinletsig_r_v=Curve.get_v_coordinatesignature.sig_r|>of_bls_scalarinletc=ifcompressedthenH.direct~input_length:2[|sig_r_u;msg|]elsehash_full[|sig_r_u;sig_r_v;msg|]inletc_check=S.eqcsig_cinletchallenge_r=Curve.add(Curve.mulCurve.onesig_u)(Curve.mulpk(Curve.Scalar.of_z@@Utils.bool_list_to_zsignature.c_bytes))inc_check&&signature.sig_r=challenge_rendopenLang_stdlibopenGadget_edwardsmoduleV:functor(L:LIB)->sigmoduleAffine:Affine_curve_intf.S_EdwardswithmoduleL=LopenLopenAffineopenEncodings(* TODO make abstract once compression is done with encodings *)typepk=pointvalpk_encoding:(P.pk,pkrepr,pk)encodingtypesignature={sig_u_bytes:boollistrepr;sig_r:pointrepr;c_bytes:boollistrepr;}valsignature_encoding:(P.signature,signature,boollist*(pk*boollist))encodingvalverify:?compressed:bool->g:pointrepr->msg:scalarrepr->pk:pkrepr->signature:signature->unit->boolreprtend=functor(L:LIB)->structmoduleAffine=MakeAffine(Curve)(L)openAffineopenLopenEncodingsopenH.V(L)typepk=pointletpoint_encoding:(Curve.t,pkrepr,pk)encoding=letcurve_base_to_sc=Lang_core.S.of_z@@Curve.Base.to_zcinletcurve_base_of_sc=Curve.Base.of_z@@Lang_core.S.to_zcinwith_implicit_bool_checkis_on_curve@@conv(funr->of_pairr)(fun(u,v)->pairuv)(func->(curve_base_to_s@@Curve.get_u_coordinatec,curve_base_to_s@@Curve.get_v_coordinatec))(fun(u,v)->Curve.from_coordinates_exn~u:(curve_base_of_su)~v:(curve_base_of_sv))(obj2_encodingscalar_encodingscalar_encoding)letpk_encoding=point_encodingtypesignature={sig_u_bytes:boollistrepr;sig_r:pointrepr;c_bytes:boollistrepr;}letsignature_encoding=conv(fun{sig_u_bytes;sig_r;c_bytes}->(sig_u_bytes,(sig_r,c_bytes)))(fun(sig_u_bytes,(sig_r,c_bytes))->{sig_u_bytes;sig_r;c_bytes})(fun({sig_u_bytes;sig_r;c_bytes}:P.signature)->(sig_u_bytes,(sig_r,c_bytes)))(fun(sig_u_bytes,(sig_r,c_bytes))->{sig_u_bytes;sig_r;c_bytes})(obj3_encoding(atomic_list_encodingbool_encoding)point_encoding(atomic_list_encodingbool_encoding))(* In the compressed variant, we drop sig_r_v of the challenge input as it
can be represented with a single bit: its parity (because of Edwards curves
are symmetric). We do so as we want to use a hash function with a fixed
input length of 2 to minimize the number of constraints. We can still prove
the security of this scheme using the Forking lemma and forking thrice.
If we really want to include sig_r_v in the challenge input, we could use
its parity bit instead of the whole scalar and compress it with the message
if it is either small or the output of a hash. This however is expensive as
we have to check the decomposition of sig_r_v with the parity bit.
Netherless, if the msg input actually is the output of the hash,
(msg = H(msg')) of a single scalar, we could fit sig_r_v in this inner hash
while keeping the full security:
c = H( g**r; msg) = H(g**r; H(msg'))
= H( r_u, r_v, H(msg'))
~ H( r_u, H(msg', r_v)) (equivalent in term of security)
Doing this would not lead to an additional compression round in the hash,
and as such the resulting overhead would be minimal (one constraint for
Poseidon128).
Assuming that max_account = max_counter = 2**32 (< 10**10) in the Transfer
circuit (privacy-team/plompiler/test/benchmark.ml), and max_amount =
max_fee = 2**64 (< 10**20), the signature message can be compressed in one
scalar:
msg = H(msg') = H(src || dst || fee || amount || counter)
len(msg') = 192 < BLS12-381.Fr order ~ 2**255
As such, we could use the above scheme to compute the challenge:
c = H(r_u, H(src || dst || fee || amount || counter, r_v)) *)lethash~compressedsig_rmsg=with_label~label:"Schnorr.hash"@@letsig_r_x=get_x_coordinatesig_rinifcompressedthendigest~input_length:2@@to_list[sig_r_x;msg]elseletsig_r_y=get_y_coordinatesig_rindigest@@to_list[sig_r_x;sig_r_y;msg](* Requires : [c_bytes] is computed correctly by the prover.
Otherwise an assert is triggered. *)(* TODO: now msg is just one scalar, it will probably be a list of scalars *)letverify?(compressed=false)~g~msg~pk~signature()=with_label~label:"Schnorr.verify"@@(* assert bytes' length *)let{sig_u_bytes;sig_r;c_bytes}=signatureinassert(List.compare_length_with(of_listsig_u_bytes)(Z.numbitsbase_order)=0);assert(List.compare_length_with(of_listc_bytes)(Z.numbitsbase_order)=0);(* generating challenge *)let*c=hash~compressedsig_rmsgin(* check challenge binary decomposition : of_bytes c_bytes = c *)let*c'=Num.scalar_of_bytesc_bytesinlet*e=equalc'cin(* re-computing randomness challenge_r = u * G_E + c * pk *)let*challenge_r=letscalars=to_list[sig_u_bytes;c_bytes]inletpoints=to_list[g;pk]inmulti_scalar_mulscalarspointsin(* check signature randomness sig_r equals challenge_r *)let*b=equalsig_rchallenge_rinBool.bandebendend