Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file context.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538(*****************************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2019-2020 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. *)(* *)(*****************************************************************************)openTezos_sapling.Core.Clientlet_=Random.self_init()moduleTez=Protocol.Alpha_context.TezmoduleShielded_tez:sigtypetvalencoding:tData_encoding.tvalpp:Format.formatter->t->unitvalzero:tvalof_mutez:int64->toptionvalto_mutez:t->int64valof_tez:Tez.t->tval(+?):t->t->ttzresultval(-?):t->t->ttzresultend=structincludeTezlet(+?)ab=a+?b|>Environment.wrap_tzresultlet(-?)ab=a-?b|>Environment.wrap_tzresultletof_tezt=leti=Tez.to_muteztinassert(UTXO.valid_amounti);WithExceptions.Option.get~loc:__LOC__@@of_muteziendletsapling_transaction_as_argt=letpp_michelsonppft=letopenTezos_michelineinletlist_of_transactions_expr=lettransaction_expr=Micheline.Bytes(0,Data_encoding.Binary.to_bytes_exnUTXO.transaction_encodingt)inMicheline.strip_locations@@Micheline.Seq(0,[transaction_expr])inMichelson_v1_printer.print_exprppflist_of_transactions_exprinFormat.asprintf"%a"pp_michelsont(** The inputs and outputs are shuffled to prevent meta-data analysis. **)moduleShuffle=structletlistl=leta=Array.of_listlinletlen=Array.lengthainfori=lendownto2doletidx=Random.intiinletswp_idx=i-1inlettmp=a.(swp_idx)ina.(swp_idx)<-a.(idx);a.(idx)<-tmpdone;Array.to_listaletpairxy=ifRandom.bool()then[y;x]else[x;y]endtypeerror+=Balance_too_lowofShielded_tez.t*Shielded_tez.tletregister_error_kindcategory~id~title~description?ppencodingfrom_errorto_error=letid="client_sapling."^Protocol.name^"."^idinregister_error_kindcategory~id~title~description?ppencodingfrom_errorto_errorlet()=register_error_kind`Temporary~id:"balance_too_low"~title:"Balance too low"~description:"The sender contract does not have enough tokens."~pp:(funppf(balance,amount)->Format.fprintfppf"@[<h>Balance too low (%a) to spend %a@]"Shielded_tez.ppbalanceShielded_tez.ppamount)Data_encoding.(obj2(req"actual_balance"Shielded_tez.encoding)(req"amount"Shielded_tez.encoding))(function|Balance_too_low(balance,amount)->Some(balance,amount)|_->None)(fun(balance,amount)->Balance_too_low(balance,amount))moduleStorage=Tezos_sapling.StoragemoduleF=Tezos_sapling.ForgemoduleInput_set=structincludeSet.Make(F.Input)letto_list=elementsletpp_fppi=Format.fprintfpp"@[<h>%s %Ld@]"(Tezos_crypto.Base58.simple_encodeViewing_key.address_b58check_encoding(F.Input.addressi))(F.Input.amounti)endmoduleAccount=structtypet={vk:Viewing_key.t;unspents:Input_set.t;balance:Shielded_tez.t;}letencoding=letopenData_encodinginconv(funcs->(cs.vk,Input_set.to_listcs.unspents,cs.balance))(fun(vk,unspents,balance)->{vk;unspents=Input_set.of_listunspents;balance})(obj3(req"vk"Viewing_key.encoding)(req"unspents"(listF.Input.encoding))(req"balance"Shielded_tez.encoding))letcreatevk={vk;unspents=Input_set.empty;balance=Shielded_tez.zero}letbalancec=c.balanceletadd_unspentcinput=letamount=WithExceptions.Option.get~loc:__LOC__@@Shielded_tez.of_mutez(F.Input.amountinput)inmatchShielded_tez.(c.balance+?amount)with|Error_->assertfalse(* overflow *)|Okbalance->letunspents=Input_set.addinputc.unspentsin{cwithbalance;unspents}letremove_unspentcinput=letamount=WithExceptions.Option.get~loc:__LOC__@@Shielded_tez.of_mutez(F.Input.amountinput)inmatchShielded_tez.(c.balance-?amount)with|Error_->assertfalse(* negative balance *)|Okbalance->letunspents=Input_set.removeinputc.unspentsin{cwithbalance;unspents}letfilter_spentaccountstorage=Input_set.fold(funinputacc->ifF.Input.is_spentinputstorageaccount.vkthenremove_unspentaccinputelseacc)account.unspentsaccountletpick_inputc=let(>?|)xf=Option.mapfxinInput_set.choosec.unspents>?|fununspent->letc=remove_unspentcunspentin(unspent,c)letpp_unspent:Format.formatter->t->unit=funppfa->(Format.pp_print_list~pp_sep:Format.pp_print_cutInput_set.pp_fppf)(Input_set.elementsa.unspents)endmoduleContract_state=structmoduleAccounts=structincludeSet.Make(structtypet=Account.tletcompareab=letopenAccountinBytes.compare(Viewing_key.to_bytesa.vk)(Viewing_key.to_bytesb.vk)end)letreplaceaset=adda(removeaset)letfindvkaccounts=find(Account.createvk)accountsendletaccounts_encoding=letopenData_encodinginconvAccounts.elements(List.fold_left(funme->Accounts.addem)Accounts.empty)(listAccount.encoding)typet={accounts:Accounts.t;storage:Storage.state}letencoding=letopenData_encodinginconv(funt->(t.accounts,t.storage))(fun(accounts,storage)->{accounts;storage})(obj2(req"accounts"accounts_encoding)(req"storage"Storage.state_encoding))letempty~memo_size={accounts=Accounts.empty;storage=Storage.empty~memo_size}letfind_accountvkcontract_state=Accounts.findvkcontract_state.accountsletinit~forcevkstate=letopenLwt_result_syntaxinAccounts.findvkstate.accounts|>function|None->letaccounts=Accounts.add(Account.createvk)state.accountsinreturn{statewithaccounts}|Some_->ifforcethenletaccounts=Accounts.add(Account.createvk)state.accountsinreturn{statewithaccounts}elsefailwith"vk already present"letadd_unspentvkinputaccounts=letaccount=Accounts.findvkaccounts|>WithExceptions.Option.get~loc:__LOC__inletaccount=Account.add_unspentaccountinputinAccounts.replaceaccountaccounts(** Scan the Sapling storage of a smart contract and update the accounts of
all known viewing keys for that contract *)letscanstatestorage=(* remove newly spent inputs *)letaccounts=Accounts.map(funaccount->Account.filter_spentaccountstorage)state.accountsin(* get all the vks that need to be scanned for *)letvks=Accounts.fold(funaccountacc->Account.(account.vk)::acc)accounts[]inletsize,_=Storage.sizestorageinletrecauxposaccounts=ifpos<sizethen(* try to decrypt each inputs with all vks *)List.fold_left(funaccvk->matchF.Input.getstorageposvkwith|None->acc|Someinput->(vk,input)::acc)[]vks|>function|[]->aux(Int64.succpos)accounts|[(vk,(_message,forge_input))]->letis_spent=F.Input.is_spentforge_inputstoragevkinifis_spentthenaux(Int64.succpos)accountselseaux(Int64.succpos)(add_unspentvkforge_inputaccounts)|_->assertfalse(* got more than one decrypting key *)elseaccountsinletcurrent_size,_=Storage.sizestate.storageinletaccounts=auxcurrent_sizeaccountsin{accounts;storage}(** Update the Sapling storage of a smart contract using a diff, checking that
the resulting Merkle tree has a root equal to the one in the diff. *)letupdate_storagecontract_state(root,diff)=letopenProtocol.Alpha_context.Saplinginletstorage=Tezos_sapling.Storage.addcontract_state.storagediff.commitments_and_ciphertextsinletcomputed_root=Storage.get_rootstorageinifcomputed_root<>rootthenStdlib.failwith"Commitment tree inconsistent wrt to node."elseletstorage=List.fold_left(funsnf->Storage.add_nullifiersnf)storagediff.nullifiersinscancontract_statestorageendmoduleClient_state=structmoduleMap=Map.Make(Protocol.Contract_hash)typet=Contract_state.tMap.tletencoding=letopenData_encodinginconvMap.bindings(List.fold_left(funm(k,v)->Map.addkvm)Map.empty)(list(obj2(req"contract"Protocol.Alpha_context.Contract.originated_encoding)(req"state"Contract_state.encoding)))letfilename="sapling_state"letload(cctxt:#Client_context.wallet)=cctxt#loadfilename~default:Map.emptyencodingletwrite(cctxt:#Client_context.wallet)t=cctxt#writefilenametencodingletget_or_init~default_memo_sizecontractclient_state=letopenLwt_result_syntaxinMap.findcontractclient_state|>function|None->(matchdefault_memo_sizewith|None->failwith"Unknown memo size for contract %s and none was provided in \
options"@@Protocol.Contract_hash.to_b58checkcontract|Somememo_size->letcontract_state=Contract_state.empty~memo_sizeinletclient_state=Map.addcontractcontract_stateclient_stateinreturn(contract_state,client_state))|Somecontract_state->return(contract_state,client_state)letregistercctxt~force~default_memo_sizecontractvk=letopenLwt_result_syntaxinlet*client_state=loadcctxtinlet*contract_state,client_state=get_or_init~default_memo_sizecontractclient_stateinlet*contract_state=Contract_state.init~forcevkcontract_stateinletclient_state=Map.addcontractcontract_stateclient_stateinwritecctxtclient_stateletfind(cctxt:#Client_context.full)contractstate=letopenLwt_result_syntaxinMap.findcontractstate|>function|None->cctxt#error"Contract %s not found"(Protocol.Contract_hash.to_b58checkcontract)|Somev->returnv(** Call the node RPC to obtain the storage diff of a contract *)letget_diffcctxtcontractoffset_commitmentoffset_nullifier=Protocol.Alpha_services.Contract.single_sapling_get_diffcctxt(cctxt#chain,cctxt#block)contract~offset_commitment~offset_nullifier()letsync_and_scancctxtcontract=letopenLwt_result_syntaxinlet*state=loadcctxtinlet*contract_state=findcctxtcontractstateinletcm_pos,nf_pos=Storage.sizecontract_state.storageinlet*diff=get_diffcctxtcontractcm_posnf_posinletcontract_state=Contract_state.update_storagecontract_statediffinletstate=Map.addcontractcontract_statestateinlet*()=writecctxtstateinreturncontract_stateend(** Truncate or pad the message to fit the memo_size *)letadjust_message_length(cctxt:#Client_context.full)?messagememo_size=letopenLwt_syntaxinmatchmessagewith|None->let+()=cctxt#warning"no message provided, adding a zeroes filled message of the required \
length: %d "memo_sizeinBytes.makememo_size'\000'|Somemessage->letmessage_length=Bytes.lengthmessageinifmessage_length=memo_sizethenreturnmessageelseifmessage_length>memo_sizethenlet+()=cctxt#warning"Your message is too long (%d bytes) and will therefore be \
truncated to %d bytes"message_lengthmemo_sizeinBytes.submessage0memo_sizeelselet+()=cctxt#warning"Your message is too short (%d bytes) and will therefore be \
right-padded with zero bytes to reach a %d-byte length"message_lengthmemo_sizeinBytes.catmessage(Bytes.make(memo_size-message_length)'\000')letcreate_payment~messagedstamount=letamount=Shielded_tez.to_mutezamountinF.make_outputdstamountmessage(** Return a list of inputs belonging to an account sufficient to cover an
amount, together with the change remaining. *)letget_shielded_amountamountaccount=letopenResult_syntaxinletbalance=Account.balanceaccountinlet+()=error_unless(balance>=amount)(Balance_too_low(balance,amount))inletto_pay=Shielded_tez.to_mutezamountinletinputs_to_spend=[]inletrecloopto_paychosen_inputsaccount=ifInt64.(compareto_payzero)>0thenAccount.pick_inputaccount|>function|None->Stdlib.failwith"Not enough inputs"(* TODO raise a proper error *)|Some(next_in,account)->letnext_val=F.Input.amountnext_ininletrest_to_pay=Int64.subto_paynext_valinlooprest_to_pay(next_in::chosen_inputs)accountelseletchange=WithExceptions.Option.get~loc:__LOC__@@Shielded_tez.of_mutez@@Int64.absto_payin(chosen_inputs,change)inloopto_payinputs_to_spendaccountletcreate_payback~memo_sizeaddressamount=letplaintext_message=Bytes.makememo_size'\000'inletamount=Shielded_tez.to_mutezamountinF.make_outputaddressamountplaintext_message(* The caller should check that the account exists already *)letunshield~src~bound_data~backdstamount(state:Contract_state.t)anti_replay=letopenResult_syntaxinletvk=Viewing_key.of_sksrcinletaccount=Contract_state.find_accountvkstate|>WithExceptions.Option.get~loc:__LOC__inlet+inputs,change=get_shielded_amountamountaccountinletmemo_size=Storage.get_memo_sizestate.storageinletpayback=create_payback~memo_sizebackdstchangeinF.forge_transaction(Shuffle.listinputs)[payback]srcanti_replay~bound_datastate.storageletshieldcctxt~dst?messageamount(state:Contract_state.t)anti_replay=letopenLwt_result_syntaxinletshielded_amount=Shielded_tez.of_tezamountinletmemo_size=Storage.get_memo_sizeContract_state.(state.storage)inlet*!message=adjust_message_lengthcctxt?messagememo_sizeinletpayment=create_payment~messagedstshielded_amountinletnegative_amount=Int64.neg(Tez.to_mutezamount)inreturn@@F.forge_shield_transaction[payment]negative_amountanti_replay~bound_data:""Contract_state.(state.storage)(* The caller should check that the account exists already *)lettransfercctxt~src~dst~backdst?messageamount(state:Contract_state.t)anti_replay=letopenLwt_result_syntaxinletvk=Viewing_key.of_sksrcinletaccount=Contract_state.find_accountvkstate|>WithExceptions.Option.get~loc:__LOC__inletmemo_size=Storage.get_memo_sizestate.storageinlet*!message=adjust_message_lengthcctxt?messagememo_sizeinlet*?inputs,change=get_shielded_amountamountaccountinletpayment=create_payment~messagedstamountinletpayback=create_payback~memo_sizebackdstchangeinletsapling_transaction=F.forge_transaction(Shuffle.listinputs)(Shuffle.pairpaybackpayment)srcanti_replay~bound_data:""state.storageinreturnsapling_transaction