(*****************************************************************************) (* *) (* Open Source License *) (* Copyright (c) 2019-2020 Nomadic Labs *) (* *) (* 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. *) (* *) (*****************************************************************************) open Client_keys open Tezos_sapling.Core.Client module Mnemonic = struct let new_random = Bip39.of_entropy (Hacl.Rand.gen 32) let to_sapling_key mnemonic = (* Z-cash needs 32 bytes and BIP-39 gives 64 bytes of entropy. We xor the two halves in case the entropy is not well distributed. *) let seed_64_to_seed_32 (seed_64 : bytes) : bytes = assert (Bytes.length seed_64 = 64) ; let first_32 = Bytes.sub seed_64 0 32 in let second_32 = Bytes.sub seed_64 32 32 in let seed_32 = Bytes.create 32 in for i = 0 to 31 do Bytes.set seed_32 i (Char.chr (Char.code (Bytes.get first_32 i) lxor Char.code (Bytes.get second_32 i))) done ; seed_32 in Spending_key.of_seed (seed_64_to_seed_32 (Bip39.to_seed mnemonic)) let words_pp = Format.(pp_print_list ~pp_sep:pp_print_space pp_print_string) end (* Transform a spending key to an uri, encrypted or not. *) let to_uri unencrypted cctxt sapling_key = if unencrypted then Lwt.return (Tezos_signer_backends.Unencrypted.make_sapling_key sapling_key) else Tezos_signer_backends.Encrypted.encrypt_sapling_key cctxt sapling_key (** Transform an uri into a spending key, asking for a password if the uri was encrypted. *) let from_uri (cctxt : #Client_context.full) uri = Tezos_signer_backends.Encrypted.decrypt_sapling_key cctxt uri let register (cctxt : #Client_context.full) ?(force = false) ?(unencrypted = false) mnemonic name = let sk = Mnemonic.to_sapling_key mnemonic in to_uri unencrypted cctxt sk >>=? fun sk_uri -> let key = { sk = sk_uri; path = [Spending_key.child_index sk]; address_index = Viewing_key.default_index; } in Sapling_key.add ~force cctxt name key >>=? fun () -> return @@ Viewing_key.of_sk sk let derive (cctxt : #Client_context.full) ?(force = false) ?(unencrypted = false) src_name dst_name child_index = Sapling_key.find cctxt src_name >>=? fun k -> from_uri cctxt k.sk >>=? fun src_sk -> let child_index = Int32.of_int child_index in let dst_sk = Spending_key.derive_key src_sk child_index in to_uri unencrypted cctxt dst_sk >>=? fun dst_sk_uri -> let dst_key = { sk = dst_sk_uri; path = child_index :: k.path; address_index = Viewing_key.default_index; } in (* TODO check this force *) let _ = force in Sapling_key.add ~force:true cctxt dst_name dst_key >>=? fun () -> let path = String.concat "/" (List.map Int32.to_string (List.rev dst_key.path)) in return (path, Viewing_key.of_sk dst_sk) let find_vk cctxt name = Sapling_key.find cctxt name >>=? fun k -> from_uri cctxt k.sk >>=? fun sk -> return (Viewing_key.of_sk sk) let new_address (cctxt : #Client_context.full) name index_opt = Sapling_key.find cctxt name >>=? fun k -> let index = match index_opt with | None -> k.address_index | Some i -> Viewing_key.index_of_int64 (Int64.of_int i) in from_uri cctxt k.sk >>=? fun sk -> return (Viewing_key.of_sk sk) >>=? fun vk -> (* Viewing_key.new_address finds the smallest index greater or equal to [index] that generates a correct address. *) let corrected_index, address = Viewing_key.new_address vk index in Sapling_key.update cctxt name {k with address_index = Viewing_key.index_succ corrected_index} >>=? fun () -> return (sk, corrected_index, address) let export_vk cctxt name = find_vk cctxt name >>=? fun vk -> return (Data_encoding.Json.construct Viewing_key.encoding vk)