ligo/src/lib_client_base/client_keys.ml

280 lines
11 KiB
OCaml
Raw Normal View History

2016-09-08 21:13:10 +04:00
(**************************************************************************)
(* *)
2017-11-14 03:36:14 +04:00
(* Copyright (c) 2014 - 2017. *)
2016-09-08 21:13:10 +04:00
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
(* *)
(* All rights reserved. No warranty, explicit or implicit, provided. *)
(* *)
(**************************************************************************)
module Public_key_hash = Client_aliases.Alias (struct
type t = Ed25519.Public_key_hash.t
let encoding = Ed25519.Public_key_hash.encoding
let of_source _ s = Lwt.return (Ed25519.Public_key_hash.of_b58check s)
let to_source _ p = return (Ed25519.Public_key_hash.to_b58check p)
2016-09-08 21:13:10 +04:00
let name = "public key hash"
end)
module Public_key = Client_aliases.Alias (struct
type t = Ed25519.Public_key.t
let encoding = Ed25519.Public_key.encoding
let of_source _ s = Lwt.return (Ed25519.Public_key.of_b58check s)
let to_source _ p = return (Ed25519.Public_key.to_b58check p)
2016-09-08 21:13:10 +04:00
let name = "public key"
end)
module Secret_key = Client_aliases.Alias (struct
type t = Ed25519.Secret_key.t
let encoding = Ed25519.Secret_key.encoding
let of_source _ s = Lwt.return (Ed25519.Secret_key.of_b58check s)
let to_source _ p = return (Ed25519.Secret_key.to_b58check p)
2016-09-08 21:13:10 +04:00
let name = "secret key"
end)
2017-11-07 20:38:11 +04:00
let gen_keys ?(force=false) ?seed (cctxt : #Client_commands.wallet) name =
let seed =
match seed with
| None -> Ed25519.Seed.generate ()
| Some s -> s in
let _, public_key, secret_key = Ed25519.generate_seeded_key seed in
2017-11-07 17:23:01 +04:00
Secret_key.add ~force cctxt name secret_key >>=? fun () ->
Public_key.add ~force cctxt name public_key >>=? fun () ->
Public_key_hash.add ~force
cctxt name (Ed25519.Public_key.hash public_key) >>=? fun () ->
return ()
2016-09-08 21:13:10 +04:00
2017-11-07 20:38:11 +04:00
let gen_keys_containing ?(prefix=false) ?(force=false) ~containing ~name (cctxt : Client_commands.full_context) =
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
let re = Str.regexp (String.concat "\\|" containing) in
(fun key -> try ignore (Str.search_forward re key 0); true
with Not_found -> false) in
let rec loop attempts =
let seed = Ed25519.Seed.generate () in
let _, public_key, secret_key = Ed25519.generate_seeded_key seed in
2017-10-15 14:42:58 +04:00
let hash = Ed25519.Public_key_hash.to_b58check @@ Ed25519.Public_key.hash public_key in
if matches hash
then
2017-11-07 17:23:01 +04:00
Secret_key.add ~force cctxt name secret_key >>=? fun () ->
Public_key.add ~force cctxt name public_key >>=? fun () ->
Public_key_hash.add ~force cctxt name (Ed25519.Public_key.hash public_key) >>=? 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
2016-09-08 21:13:10 +04:00
let check_keys_consistency pk sk =
let message = MBytes.of_string "Voulez-vous coucher avec moi, ce soir ?" in
let signature = Ed25519.sign sk message in
Ed25519.Signature.check pk signature message
2016-09-08 21:13:10 +04:00
2017-11-07 20:38:11 +04:00
let get_key (cctxt : #Client_commands.wallet) pkh =
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 ->
Public_key.find cctxt n >>=? fun pk ->
Secret_key.find cctxt n >>=? fun sk ->
2016-09-08 21:13:10 +04:00
return (n, pk, sk)
2017-11-07 20:38:11 +04:00
let get_keys (wallet : #Client_commands.wallet) =
Secret_key.load wallet >>=? fun sks ->
2017-04-11 00:58:36 +04:00
Lwt_list.filter_map_s
(fun (name, sk) ->
2017-04-11 00:58:36 +04:00
begin
2017-11-07 20:38:11 +04:00
Public_key.find wallet name >>=? fun pk ->
Public_key_hash.find wallet name >>=? fun pkh ->
2017-04-11 00:58:36 +04:00
return (name, pkh, pk, sk)
end >>= function
| Ok r -> Lwt.return (Some r)
| Error _ -> Lwt.return_none)
sks >>= fun keys ->
return keys
let list_keys cctxt =
Public_key_hash.load cctxt >>=? fun l ->
map_s
(fun (name, pkh) ->
Public_key.mem cctxt name >>=? fun pkm ->
Secret_key.mem cctxt name >>=? fun pks ->
return (name, pkh, pkm, pks))
l
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
2017-09-15 17:18:00 +04:00
then
Public_key.find_opt cctxt name >>=? fun pkm ->
Secret_key.find_opt cctxt name >>=? fun pks ->
return (Some (pkh, pkm, pks))
else find_key tl
in find_key l
let force_switch =
Client_commands.force_switch ~doc:"overwrite existing keys" ()
let group =
{ Cli_entries.name = "keys" ;
title = "Commands for managing the wallet of cryptographic keys" }
2016-09-08 21:13:10 +04:00
let commands () =
let open Cli_entries in
let show_private_switch =
switch
~parameter:"-show-secret"
~doc:"show the private key" in
[
command ~group ~desc: "Generate a pair of keys."
(args1 Secret_key.force_switch)
2016-09-08 21:13:10 +04:00
(prefixes [ "gen" ; "keys" ]
@@ Secret_key.fresh_alias_param
@@ stop)
2017-11-07 20:38:11 +04:00
(fun force name (cctxt : Client_commands.full_context) ->
2017-11-07 17:23:01 +04:00
Secret_key.of_fresh cctxt force name >>=? fun name ->
gen_keys ~force cctxt name) ;
2017-10-15 14:42:58 +04:00
command ~group ~desc: "Generate keys including the given string"
(args2
(switch ~doc:"the key must begin with tz1[word]" ~parameter:"-prefix")
force_switch)
2017-10-15 14:42:58 +04:00
(prefixes [ "gen" ; "vanity" ; "keys" ]
@@ Public_key_hash.fresh_alias_param
@@ prefix "matching"
@@ (seq_of_param @@ string ~name:"words" ~desc:"string key must contain one of these words"))
2017-11-07 17:23:01 +04:00
(fun (prefix, force) name containing cctxt ->
Public_key_hash.of_fresh cctxt force name >>=? fun name ->
gen_keys_containing ~force ~prefix ~containing ~name cctxt) ;
2017-10-15 14:42:58 +04:00
command ~group ~desc: "Add a secret key to the wallet."
(args1 Secret_key.force_switch)
2016-09-08 21:13:10 +04:00
(prefixes [ "add" ; "secret" ; "key" ]
@@ Secret_key.fresh_alias_param
@@ Secret_key.source_param
@@ stop)
2017-11-07 17:23:01 +04:00
(fun force name sk cctxt ->
Secret_key.of_fresh cctxt force name >>=? fun name ->
Public_key.find_opt cctxt name >>=? function
| None ->
let pk = Ed25519.Secret_key.to_public_key sk in
2017-11-07 17:23:01 +04:00
Public_key_hash.add ~force cctxt
name (Ed25519.Public_key.hash pk) >>=? fun () ->
2017-11-07 17:23:01 +04:00
Public_key.add ~force cctxt name pk >>=? fun () ->
Secret_key.add ~force cctxt name sk
| Some pk ->
fail_unless
2017-11-07 17:23:01 +04:00
(check_keys_consistency pk sk || force)
(failure
"public and secret keys '%s' don't correspond, \
2017-10-15 14:42:58 +04:00
please don't use -force" name) >>=? fun () ->
2017-11-07 17:23:01 +04:00
Secret_key.add ~force cctxt name sk) ;
command ~group ~desc: "Add a public key to the wallet."
(args1 Public_key.force_switch)
2017-11-07 20:38:11 +04:00
(prefixes [ "add" ; "public" ; "key" ]
@@ Public_key.fresh_alias_param
@@ Public_key.source_param
@@ stop)
(fun force name key cctxt ->
Public_key.of_fresh cctxt force name >>=? fun name ->
Public_key_hash.add ~force cctxt
name (Ed25519.Public_key.hash key) >>=? fun () ->
Public_key.add ~force cctxt name key) ;
command ~group ~desc: "Add a public key to the wallet."
(args1 Public_key.force_switch)
2016-09-08 21:13:10 +04:00
(prefixes [ "add" ; "identity" ]
@@ Public_key_hash.fresh_alias_param
@@ Public_key_hash.source_param
@@ stop)
2017-11-07 17:23:01 +04:00
(fun force name hash cctxt ->
Public_key_hash.of_fresh cctxt force name >>=? fun name ->
Public_key_hash.add ~force cctxt name hash) ;
command ~group ~desc: "List all public key hashes and associated keys."
no_options
2016-09-08 21:13:10 +04:00
(fixed [ "list" ; "known" ; "identities" ])
2017-11-07 20:38:11 +04:00
(fun () (cctxt : Client_commands.full_context) ->
list_keys cctxt >>=? fun l ->
iter_s
(fun (name, pkh, pkm, pks) ->
Public_key_hash.to_source cctxt pkh >>=? fun v ->
2017-11-07 20:38:11 +04:00
cctxt#message "%s: %s%s%s" name v
(if pkm then " (public key known)" else "")
(if pks then " (secret key known)" else "") >>= fun () ->
return ())
l) ;
command ~group ~desc: "Show the keys associated with an identity."
(args1 show_private_switch)
2017-09-15 17:18:00 +04:00
(prefixes [ "show" ; "identity"]
@@ Public_key_hash.alias_param
@@ stop)
2017-11-07 20:38:11 +04:00
(fun show_private (name, _) (cctxt : Client_commands.full_context) ->
2017-09-15 17:18:00 +04:00
let ok_lwt x = x >>= (fun x -> return x) in
alias_keys cctxt name >>=? fun key_info ->
match key_info with
2017-11-07 20:38:11 +04:00
| None -> ok_lwt @@ cctxt#message "No keys found for identity"
2017-09-15 17:18:00 +04:00
| Some (hash, pub, priv) ->
Public_key_hash.to_source cctxt hash >>=? fun hash ->
2017-11-07 20:38:11 +04:00
ok_lwt @@ cctxt#message "Hash: %s" hash >>=? fun () ->
2017-09-15 17:18:00 +04:00
match pub with
| None -> return ()
| Some pub ->
Public_key.to_source cctxt pub >>=? fun pub ->
2017-11-07 20:38:11 +04:00
ok_lwt @@ cctxt#message "Public Key: %s" pub >>=? fun () ->
if show_private then
2017-09-15 17:18:00 +04:00
match priv with
| None -> return ()
| Some priv ->
Secret_key.to_source cctxt priv >>=? fun priv ->
2017-11-07 20:38:11 +04:00
ok_lwt @@ cctxt#message "Secret Key: %s" priv
2017-09-15 17:18:00 +04:00
else return ()) ;
command ~group ~desc: "Forget the entire wallet of keys."
(args1 (Client_commands.force_switch ~doc:"you got to use the force for that" ()))
2016-09-08 21:13:10 +04:00
(fixed [ "forget" ; "all" ; "keys" ])
2017-11-07 17:23:01 +04:00
(fun force cctxt ->
fail_unless force
2017-11-07 20:38:11 +04:00
(failure "this can only used with option -force") >>=? fun () ->
Public_key.set cctxt [] >>=? fun () ->
Secret_key.set cctxt [] >>=? fun () ->
Public_key_hash.set cctxt []) ;
]