2016-09-08 21:13:10 +04:00
|
|
|
(**************************************************************************)
|
|
|
|
(* *)
|
2018-02-06 00:17:03 +04:00
|
|
|
(* Copyright (c) 2014 - 2018. *)
|
2016-09-08 21:13:10 +04:00
|
|
|
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
|
|
|
|
(* *)
|
|
|
|
(* All rights reserved. No warranty, explicit or implicit, provided. *)
|
|
|
|
(* *)
|
|
|
|
(**************************************************************************)
|
|
|
|
|
2018-02-01 20:31:08 +04:00
|
|
|
type error += Unregistered_key_scheme of string
|
2018-04-12 19:57:08 +04:00
|
|
|
|
2018-02-01 20:31:08 +04:00
|
|
|
let () =
|
|
|
|
register_error_kind `Permanent
|
|
|
|
~id: "cli.unregistered_key_scheme"
|
|
|
|
~title: "Unregistered key scheme"
|
|
|
|
~description: "A key has been provided with an \
|
|
|
|
unregistered scheme (no corresponding plugin)"
|
|
|
|
~pp:
|
|
|
|
(fun ppf s ->
|
|
|
|
Format.fprintf ppf "No matching plugin for key scheme %s" s)
|
|
|
|
Data_encoding.(obj1 (req "value" string))
|
|
|
|
(function Unregistered_key_scheme s -> Some s | _ -> None)
|
|
|
|
(fun s -> Unregistered_key_scheme s)
|
|
|
|
|
2016-09-08 21:13:10 +04:00
|
|
|
module Public_key_hash = Client_aliases.Alias (struct
|
2018-04-05 19:35:35 +04:00
|
|
|
type t = Signature.Public_key_hash.t
|
|
|
|
let encoding = Signature.Public_key_hash.encoding
|
|
|
|
let of_source s = Lwt.return (Signature.Public_key_hash.of_b58check s)
|
|
|
|
let to_source p = return (Signature.Public_key_hash.to_b58check p)
|
2016-09-08 21:13:10 +04:00
|
|
|
let name = "public key hash"
|
|
|
|
end)
|
|
|
|
|
2018-04-12 19:57:08 +04:00
|
|
|
type location = string list
|
|
|
|
|
2018-02-01 20:31:08 +04:00
|
|
|
module type LOCATOR = sig
|
|
|
|
val name : string
|
|
|
|
type t
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2018-04-12 19:57:08 +04:00
|
|
|
val create : scheme:string -> location:location -> t
|
2018-02-01 20:31:08 +04:00
|
|
|
val scheme : t -> string
|
2018-04-12 19:57:08 +04:00
|
|
|
val location : t -> location
|
2018-02-01 20:31:08 +04:00
|
|
|
val to_string : t -> string
|
|
|
|
val pp : Format.formatter -> t -> unit
|
|
|
|
end
|
|
|
|
|
2018-04-12 19:57:08 +04:00
|
|
|
type sk_locator = Sk_locator of { scheme : string ; location : location }
|
|
|
|
type pk_locator = Pk_locator of { scheme : string ; location : location }
|
2018-02-01 20:31:08 +04:00
|
|
|
|
|
|
|
module Sk_locator = struct
|
|
|
|
let name = "secret key"
|
|
|
|
type t = sk_locator
|
|
|
|
|
|
|
|
let create ~scheme ~location =
|
|
|
|
Sk_locator { scheme ; location }
|
|
|
|
|
|
|
|
let scheme (Sk_locator { scheme }) = scheme
|
|
|
|
let location (Sk_locator { location }) = location
|
|
|
|
|
|
|
|
let to_string (Sk_locator { scheme ; location }) =
|
2018-04-12 19:57:08 +04:00
|
|
|
String.concat "/" ((scheme ^ ":") :: List.map Uri.pct_encode location)
|
2018-02-01 20:31:08 +04:00
|
|
|
|
2018-04-12 19:57:08 +04:00
|
|
|
let pp ppf loc =
|
|
|
|
Format.pp_print_string ppf (to_string loc)
|
2018-02-01 20:31:08 +04:00
|
|
|
end
|
|
|
|
|
|
|
|
module Pk_locator = struct
|
|
|
|
let name = "public key"
|
|
|
|
type t = pk_locator
|
|
|
|
|
|
|
|
let create ~scheme ~location =
|
|
|
|
Pk_locator { scheme ; location }
|
|
|
|
|
|
|
|
let scheme (Pk_locator { scheme }) = scheme
|
|
|
|
let location (Pk_locator { location }) = location
|
|
|
|
|
|
|
|
let to_string (Pk_locator { scheme ; location }) =
|
2018-04-12 19:57:08 +04:00
|
|
|
String.concat "/" ((scheme ^ ":") :: List.map Uri.pct_encode location)
|
2018-02-01 20:31:08 +04:00
|
|
|
|
2018-04-12 19:57:08 +04:00
|
|
|
let pp ppf loc =
|
|
|
|
Format.pp_print_string ppf (to_string loc)
|
2018-02-01 20:31:08 +04:00
|
|
|
end
|
|
|
|
|
|
|
|
module type KEY = sig
|
|
|
|
type t
|
|
|
|
val to_b58check : t -> string
|
|
|
|
val of_b58check_exn : string -> t
|
|
|
|
end
|
|
|
|
|
|
|
|
module Locator (K : KEY) (L : LOCATOR) = struct
|
|
|
|
include L
|
|
|
|
|
|
|
|
let of_unencrypted k =
|
|
|
|
L.create ~scheme:"unencrypted"
|
2018-04-12 19:57:08 +04:00
|
|
|
~location:[K.to_b58check k]
|
2018-02-01 20:31:08 +04:00
|
|
|
|
|
|
|
let of_string s =
|
|
|
|
match String.index s ':' with
|
|
|
|
| exception Not_found ->
|
|
|
|
of_unencrypted (K.of_b58check_exn s)
|
|
|
|
| i ->
|
|
|
|
let len = String.length s in
|
2018-04-12 19:57:08 +04:00
|
|
|
let scheme = String.sub s 0 i in
|
|
|
|
let location =
|
|
|
|
String.sub s (i+1) (len-i-1) |>
|
|
|
|
String.split '/' |>
|
|
|
|
List.map Uri.pct_decode |>
|
|
|
|
List.filter ((<>) "") in
|
|
|
|
create ~scheme ~location
|
2018-02-01 20:31:08 +04:00
|
|
|
|
|
|
|
let of_source s = return (of_string s)
|
|
|
|
let to_source t = return (to_string t)
|
|
|
|
|
|
|
|
let encoding = Data_encoding.(conv to_string of_string string)
|
|
|
|
end
|
|
|
|
|
2018-04-05 19:35:35 +04:00
|
|
|
module Secret_key_locator = Locator(Signature.Secret_key)(Sk_locator)
|
2018-02-01 20:31:08 +04:00
|
|
|
module Secret_key = Client_aliases.Alias (Secret_key_locator)
|
2018-04-05 19:35:35 +04:00
|
|
|
module Public_key_locator = Locator(Signature.Public_key)(Pk_locator)
|
2018-02-01 20:31:08 +04:00
|
|
|
module Public_key = Client_aliases.Alias (Public_key_locator)
|
|
|
|
|
|
|
|
module type SIGNER = sig
|
|
|
|
type secret_key
|
|
|
|
type public_key
|
|
|
|
val scheme : string
|
2018-02-02 01:43:09 +04:00
|
|
|
val title : string
|
|
|
|
val description : string
|
2018-02-08 22:00:01 +04:00
|
|
|
val init : #Client_context.io_wallet -> unit tzresult Lwt.t
|
|
|
|
val sk_locator_of_human_input : #Client_context.io_wallet -> string list -> sk_locator tzresult Lwt.t
|
|
|
|
val pk_locator_of_human_input : #Client_context.io_wallet -> string list -> pk_locator tzresult Lwt.t
|
2018-02-01 20:31:08 +04:00
|
|
|
val sk_of_locator : sk_locator -> secret_key tzresult Lwt.t
|
|
|
|
val pk_of_locator : pk_locator -> public_key tzresult Lwt.t
|
|
|
|
val sk_to_locator : secret_key -> sk_locator Lwt.t
|
|
|
|
val pk_to_locator : public_key -> pk_locator Lwt.t
|
|
|
|
val neuterize : secret_key -> public_key Lwt.t
|
2018-04-12 19:57:08 +04:00
|
|
|
val public_key : public_key -> Signature.Public_key.t tzresult Lwt.t
|
|
|
|
val public_key_hash : public_key -> Signature.Public_key_hash.t tzresult Lwt.t
|
2018-05-25 17:50:31 +04:00
|
|
|
val sign :
|
|
|
|
?watermark: Signature.watermark ->
|
|
|
|
secret_key -> MBytes.t -> Signature.t tzresult Lwt.t
|
2018-02-01 20:31:08 +04:00
|
|
|
end
|
|
|
|
|
2018-02-08 22:00:01 +04:00
|
|
|
let signers_table : (string, (module SIGNER) * bool) Hashtbl.t = Hashtbl.create 13
|
|
|
|
|
2018-02-01 20:31:08 +04:00
|
|
|
let register_signer signer =
|
|
|
|
let module Signer = (val signer : SIGNER) in
|
2018-02-08 22:00:01 +04:00
|
|
|
Hashtbl.replace signers_table Signer.scheme (signer, false)
|
2018-02-01 20:31:08 +04:00
|
|
|
|
2018-02-08 22:00:01 +04:00
|
|
|
let find_signer_for_key cctxt ~scheme =
|
2018-02-01 20:31:08 +04:00
|
|
|
match Hashtbl.find signers_table scheme with
|
2018-04-12 19:57:08 +04:00
|
|
|
| exception Not_found ->
|
|
|
|
fail (Unregistered_key_scheme scheme)
|
2018-02-08 22:00:01 +04:00
|
|
|
| signer, false ->
|
|
|
|
let module Signer = (val signer : SIGNER) in
|
|
|
|
Signer.init cctxt >>=? fun () ->
|
2018-04-09 18:40:56 +04:00
|
|
|
Hashtbl.replace signers_table scheme (signer, true) ;
|
2018-02-08 22:00:01 +04:00
|
|
|
return signer
|
|
|
|
| signer, true -> return signer
|
2018-02-01 20:31:08 +04:00
|
|
|
|
2018-02-14 14:01:23 +04:00
|
|
|
let registered_signers () : (string * (module SIGNER)) list =
|
2018-02-08 22:00:01 +04:00
|
|
|
Hashtbl.fold (fun k (v, _) acc -> (k, v) :: acc) signers_table []
|
2018-02-14 14:01:23 +04:00
|
|
|
|
2018-04-12 19:57:08 +04:00
|
|
|
type error += Signature_mismatch of Secret_key_locator.t
|
|
|
|
|
|
|
|
let () =
|
|
|
|
register_error_kind `Permanent
|
|
|
|
~id: "cli.signature_mismatch"
|
|
|
|
~title: "Signature mismatch"
|
|
|
|
~description: "The signer produced an invalid signature"
|
|
|
|
~pp:
|
|
|
|
(fun ppf sk ->
|
|
|
|
Format.fprintf ppf
|
|
|
|
"The signer for %a produced an invalid signature"
|
|
|
|
Secret_key_locator.pp sk)
|
|
|
|
Data_encoding.(obj1 (req "locator" Secret_key_locator.encoding))
|
|
|
|
(function Signature_mismatch sk -> Some sk | _ -> None)
|
|
|
|
(fun sk -> Signature_mismatch sk)
|
|
|
|
|
2018-05-25 17:50:31 +04:00
|
|
|
let sign ?watermark cctxt ((Sk_locator { scheme }) as skloc) buf =
|
2018-02-08 22:00:01 +04:00
|
|
|
find_signer_for_key cctxt ~scheme >>=? fun signer ->
|
2018-02-01 20:31:08 +04:00
|
|
|
let module Signer = (val signer : SIGNER) in
|
|
|
|
Signer.sk_of_locator skloc >>=? fun t ->
|
2018-04-12 19:57:08 +04:00
|
|
|
Signer.sign ?watermark t buf >>=? fun signature ->
|
|
|
|
Signer.neuterize t >>= fun pk ->
|
|
|
|
Signer.public_key pk >>=? fun pubkey ->
|
|
|
|
fail_unless
|
2018-05-24 04:26:10 +04:00
|
|
|
(Signature.check ?watermark pubkey signature buf)
|
2018-04-12 19:57:08 +04:00
|
|
|
(Signature_mismatch skloc) >>=? fun () ->
|
|
|
|
return signature
|
2018-02-01 20:31:08 +04:00
|
|
|
|
2018-05-25 17:50:31 +04:00
|
|
|
let append ?watermark cctxt loc buf =
|
|
|
|
sign ?watermark cctxt loc buf >>|? fun signature ->
|
2018-04-05 19:35:35 +04:00
|
|
|
Signature.concat buf signature
|
|
|
|
|
2018-04-17 12:50:23 +04:00
|
|
|
let register_key cctxt ?(force=false)
|
|
|
|
(public_key_hash, public_key, secret_key) name =
|
2018-02-01 20:31:08 +04:00
|
|
|
Secret_key.add ~force cctxt name
|
|
|
|
(Secret_key_locator.of_unencrypted secret_key) >>=? fun () ->
|
|
|
|
Public_key.add ~force cctxt name
|
|
|
|
(Public_key_locator.of_unencrypted public_key) >>=? fun () ->
|
2017-11-07 17:23:01 +04:00
|
|
|
Public_key_hash.add ~force
|
2018-04-05 19:35:35 +04:00
|
|
|
cctxt name public_key_hash >>=? fun () ->
|
2017-04-05 01:35:41 +04:00
|
|
|
return ()
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2018-04-17 12:50:23 +04:00
|
|
|
let gen_keys ?(force=false) ?algo ?seed (cctxt : #Client_context.io_wallet) name =
|
|
|
|
let key = Signature.generate_key ?algo ?seed () in
|
|
|
|
register_key cctxt ~force key name
|
2018-04-05 19:35:35 +04:00
|
|
|
|
2018-05-26 12:52:34 +04:00
|
|
|
let gen_keys_containing ?(prefix=false) ?(force=false) ~containing ~name (cctxt : #Client_context.io_wallet) =
|
2017-10-15 14:42:58 +04:00
|
|
|
let unrepresentable =
|
|
|
|
List.filter (fun s -> not @@ Base58.Alphabet.all_in_alphabet Base58.Alphabet.bitcoin s) containing in
|
|
|
|
match unrepresentable with
|
|
|
|
| _ :: _ ->
|
2017-11-07 20:38:11 +04:00
|
|
|
cctxt#warning
|
2017-10-15 14:42:58 +04:00
|
|
|
"The following can't be written in the key alphabet (%a): %a"
|
|
|
|
Base58.Alphabet.pp Base58.Alphabet.bitcoin
|
|
|
|
(Format.pp_print_list
|
|
|
|
~pp_sep:(fun ppf () -> Format.fprintf ppf ", ")
|
|
|
|
(fun ppf s -> Format.fprintf ppf "'%s'" s))
|
|
|
|
unrepresentable >>= return
|
|
|
|
| [] ->
|
|
|
|
Public_key_hash.mem cctxt name >>=? fun name_exists ->
|
2017-11-07 17:23:01 +04:00
|
|
|
if name_exists && not force
|
2017-10-15 14:42:58 +04:00
|
|
|
then
|
2017-11-07 20:38:11 +04:00
|
|
|
cctxt#warning
|
2017-10-15 14:42:58 +04:00
|
|
|
"Key for name '%s' already exists. Use -force to update." name >>= return
|
|
|
|
else
|
|
|
|
begin
|
2017-11-07 20:38:11 +04:00
|
|
|
cctxt#warning "This process uses a brute force search and \
|
2017-10-15 14:42:58 +04:00
|
|
|
may take a long time to find a key." >>= fun () ->
|
|
|
|
let matches =
|
|
|
|
if prefix then
|
|
|
|
let containing_tz1 = List.map ((^) "tz1") containing in
|
|
|
|
(fun key -> List.exists
|
|
|
|
(fun containing ->
|
|
|
|
String.sub key 0 (String.length containing) = containing)
|
|
|
|
containing_tz1)
|
|
|
|
else
|
2018-03-07 11:40:02 +04:00
|
|
|
let re = Re.Str.regexp (String.concat "\\|" containing) in
|
|
|
|
(fun key -> try ignore (Re.Str.search_forward re key 0); true
|
2017-10-15 14:42:58 +04:00
|
|
|
with Not_found -> false) in
|
|
|
|
let rec loop attempts =
|
2018-04-05 19:35:35 +04:00
|
|
|
let public_key_hash, public_key, secret_key =
|
2018-04-06 01:22:30 +04:00
|
|
|
Signature.generate_key () in
|
2018-04-05 19:35:35 +04:00
|
|
|
let hash = Signature.Public_key_hash.to_b58check @@
|
|
|
|
Signature.Public_key.hash public_key in
|
2017-10-15 14:42:58 +04:00
|
|
|
if matches hash
|
|
|
|
then
|
2018-02-01 20:31:08 +04:00
|
|
|
Secret_key.add ~force cctxt name
|
|
|
|
(Secret_key_locator.of_unencrypted secret_key) >>=? fun () ->
|
|
|
|
Public_key.add ~force cctxt name
|
|
|
|
(Public_key_locator.of_unencrypted public_key) >>=? fun () ->
|
2018-04-05 19:35:35 +04:00
|
|
|
Public_key_hash.add ~force cctxt name public_key_hash >>=? fun () ->
|
2017-10-15 14:42:58 +04:00
|
|
|
return hash
|
|
|
|
else begin if attempts mod 25_000 = 0
|
2017-11-07 20:38:11 +04:00
|
|
|
then cctxt#message "Tried %d keys without finding a match" attempts
|
2017-10-15 14:42:58 +04:00
|
|
|
else Lwt.return () end >>= fun () ->
|
|
|
|
loop (attempts + 1) in
|
|
|
|
loop 1 >>=? fun key_hash ->
|
2017-11-07 20:38:11 +04:00
|
|
|
cctxt#message
|
2017-10-15 14:42:58 +04:00
|
|
|
"Generated '%s' under the name '%s'." key_hash name >>= fun () ->
|
|
|
|
return ()
|
|
|
|
end
|
|
|
|
|
2018-02-14 18:20:03 +04:00
|
|
|
let get_key (cctxt : #Client_context.wallet) pkh =
|
2017-04-05 03:02:10 +04:00
|
|
|
Public_key_hash.rev_find cctxt pkh >>=? function
|
2017-11-07 20:38:11 +04:00
|
|
|
| None -> failwith "no keys for the source contract manager"
|
2016-09-08 21:13:10 +04:00
|
|
|
| Some n ->
|
2017-04-05 03:02:10 +04:00
|
|
|
Public_key.find cctxt n >>=? fun pk ->
|
|
|
|
Secret_key.find cctxt n >>=? fun sk ->
|
2018-02-01 20:31:08 +04:00
|
|
|
let scheme = Secret_key_locator.scheme sk in
|
2018-02-08 22:00:01 +04:00
|
|
|
find_signer_for_key cctxt ~scheme >>=? fun signer ->
|
2018-02-01 20:31:08 +04:00
|
|
|
let module Signer = (val signer : SIGNER) in
|
|
|
|
Signer.pk_of_locator pk >>=? fun pk ->
|
2018-04-12 19:57:08 +04:00
|
|
|
Signer.public_key pk >>=? fun pk ->
|
2016-09-08 21:13:10 +04:00
|
|
|
return (n, pk, sk)
|
|
|
|
|
2018-02-08 22:00:01 +04:00
|
|
|
let get_keys (wallet : #Client_context.io_wallet) =
|
2017-11-07 20:38:11 +04:00
|
|
|
Secret_key.load wallet >>=? fun sks ->
|
2018-02-01 20:31:08 +04:00
|
|
|
Lwt_list.filter_map_s begin fun (name, sk) ->
|
|
|
|
begin
|
|
|
|
Public_key.find wallet name >>=? fun pk ->
|
|
|
|
Public_key_hash.find wallet name >>=? fun pkh ->
|
|
|
|
let scheme = Public_key_locator.scheme pk in
|
2018-02-08 22:00:01 +04:00
|
|
|
find_signer_for_key wallet ~scheme >>=? fun signer ->
|
2018-02-01 20:31:08 +04:00
|
|
|
let module Signer = (val signer : SIGNER) in
|
|
|
|
Signer.pk_of_locator pk >>=? fun pk ->
|
2018-04-12 19:57:08 +04:00
|
|
|
Signer.public_key pk >>=? fun pk ->
|
2018-02-01 20:31:08 +04:00
|
|
|
return (name, pkh, pk, sk)
|
|
|
|
end >>= function
|
|
|
|
| Ok r -> Lwt.return (Some r)
|
|
|
|
| Error _ -> Lwt.return_none
|
|
|
|
end sks >>= fun keys ->
|
2017-04-11 00:58:36 +04:00
|
|
|
return keys
|
2017-02-28 11:18:06 +04:00
|
|
|
|
2017-01-12 19:13:03 +04:00
|
|
|
let list_keys cctxt =
|
2017-04-05 03:02:10 +04:00
|
|
|
Public_key_hash.load cctxt >>=? fun l ->
|
|
|
|
map_s
|
|
|
|
(fun (name, pkh) ->
|
2018-02-01 20:31:08 +04:00
|
|
|
Public_key.find_opt cctxt name >>=? fun pkm ->
|
|
|
|
Secret_key.find_opt cctxt name >>=? fun pks ->
|
2017-04-05 03:02:10 +04:00
|
|
|
return (name, pkh, pkm, pks))
|
2017-01-12 19:13:03 +04:00
|
|
|
l
|
2017-02-28 11:18:06 +04:00
|
|
|
|
2017-09-15 17:18:00 +04:00
|
|
|
let alias_keys cctxt name =
|
|
|
|
Public_key_hash.load cctxt >>=? fun l ->
|
|
|
|
let rec find_key = function
|
|
|
|
| [] -> return None
|
|
|
|
| (key_name, pkh) :: tl ->
|
2017-11-13 17:29:28 +04:00
|
|
|
if key_name = name
|
2018-04-12 19:57:08 +04:00
|
|
|
then begin
|
2017-09-15 17:18:00 +04:00
|
|
|
Public_key.find_opt cctxt name >>=? fun pkm ->
|
|
|
|
Secret_key.find_opt cctxt name >>=? fun pks ->
|
|
|
|
return (Some (pkh, pkm, pks))
|
2018-04-12 19:57:08 +04:00
|
|
|
end
|
2017-09-15 17:18:00 +04:00
|
|
|
else find_key tl
|
|
|
|
in find_key l
|
|
|
|
|
2018-02-11 22:17:39 +04:00
|
|
|
let force_switch () =
|
2018-04-03 13:39:09 +04:00
|
|
|
Clic.switch
|
2018-02-14 18:54:52 +04:00
|
|
|
~long:"force" ~short:'f'
|
|
|
|
~doc:"overwrite existing keys" ()
|