Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Page
Library
Module
Module type
Parameter
Class
Class type
Source
client_keys_commands.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006
(*****************************************************************************) (* *) (* Open Source License *) (* Copyright (c) 2018 Dynamic Ledger Solutions, Inc. <contact@tezos.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. *) (* *) (*****************************************************************************) open Client_keys let group = { Tezos_clic.name = "keys"; title = "Commands for managing the wallet of cryptographic keys"; } let algo_param () = let open Lwt_result_syntax in Tezos_clic.parameter ~autocomplete:(fun _ -> return ["ed25519"; "secp256k1"; "p256"; "bls"]) (fun _ name -> match name with | "ed25519" -> return Signature.Ed25519 | "secp256k1" -> return Signature.Secp256k1 | "p256" -> return Signature.P256 | "bls" -> return Signature.Bls | name -> failwith "Unknown signature algorithm (%s). Available: 'ed25519', \ 'secp256k1','p256' or 'bls'" name) let sig_algo_arg = Tezos_clic.default_arg ~doc:"use custom signature algorithm" ~long:"sig" ~short:'s' ~placeholder:"ed25519|secp256k1|p256|bls" ~default:"ed25519" (algo_param ()) let gen_keys_containing ?(encrypted = false) ?(prefix = false) ?(ignore_case = false) ?(force = false) ~containing ~name (cctxt : #Client_context.io_wallet) = let open Lwt_result_syntax in let unrepresentable = List.filter (fun s -> not @@ Tezos_crypto.Base58.Alphabet.all_in_alphabet ~ignore_case Tezos_crypto.Base58.Alphabet.bitcoin s) containing in let good_initial_char = "KLMNPQRSTUVWXYZabcdefghi" in let bad_initial_char = if ignore_case then "123456789Jj" else "123456789ABCDEFGHJjkmnopqrstuvwxyz" in let containing = if ignore_case then List.map String.lowercase_ascii containing else containing in match unrepresentable with | _ :: _ -> cctxt#error "@[<v 0>The following words can't be written in the key alphabet: %a.@,\ Valid characters: %a@,\ Extra restriction for the first character: %s@]" (Format.pp_print_list ~pp_sep:(fun ppf () -> Format.fprintf ppf ", ") (fun ppf s -> Format.fprintf ppf "'%s'" s)) unrepresentable Tezos_crypto.Base58.Alphabet.pp Tezos_crypto.Base58.Alphabet.bitcoin good_initial_char | [] -> ( let unrepresentable = List.filter (fun s -> prefix && String.contains bad_initial_char s.[0]) containing in match unrepresentable with | _ :: _ -> cctxt#error "@[<v 0>The following words don't respect the first character \ restriction: %a.@,\ Valid characters: %a@,\ Extra restriction for the first character: %s@]" (Format.pp_print_list ~pp_sep:(fun ppf () -> Format.fprintf ppf ", ") (fun ppf s -> Format.fprintf ppf "'%s'" s)) unrepresentable Tezos_crypto.Base58.Alphabet.pp Tezos_crypto.Base58.Alphabet.bitcoin good_initial_char | [] -> let* name_exists = Public_key_hash.mem cctxt name in if name_exists && not force then let*! () = cctxt#warning "Key for name '%s' already exists. Use --force to update." name in return_unit else let*! () = cctxt#warning "This process uses a brute force search and may take a long \ time to find a key." in let adjust_case = if ignore_case then String.lowercase_ascii else Fun.id in let matches = if prefix then let containing_tz1 = List.map (( ^ ) "tz1") containing in fun key -> List.exists (fun containing -> String.sub (adjust_case key) 0 (String.length containing) = containing) containing_tz1 else let re = Re.Str.regexp (String.concat "\\|" containing) in fun key -> try ignore (Re.Str.search_forward re (adjust_case key) 0) ; true with Not_found -> false in let rec loop attempts = let public_key_hash, public_key, secret_key = Signature.generate_key () in let hash = Signature.Public_key_hash.to_b58check @@ Signature.Public_key.hash public_key in if matches hash then let*? pk_uri = Tezos_signer_backends.Unencrypted.make_pk public_key in let* sk_uri = if encrypted then Tezos_signer_backends.Encrypted.prompt_twice_and_encrypt cctxt secret_key else Lwt.return (Tezos_signer_backends.Unencrypted.make_sk secret_key) in let* () = register_key cctxt ~force (public_key_hash, pk_uri, sk_uri) ~public_key name in return hash else let*! () = if attempts mod 25_000 = 0 then cctxt#message "Tried %d keys without finding a match" attempts else Lwt.return_unit in let*! () = Lwt.pause () in loop (attempts + 1) in let* key_hash = loop 1 in let*! () = cctxt#message "Generated '%s' under the name '%s'." key_hash name in return_unit) let rec input_fundraiser_params (cctxt : #Client_context.io_wallet) = let open Lwt_result_syntax in let rec get_boolean_answer (cctxt : #Client_context.io_wallet) ~default ~msg = let prompt = if default then "(Y/n/q)" else "(y/N/q)" in let* gen = cctxt#prompt "%s %s: " msg prompt in match (default, String.lowercase_ascii gen) with | default, "" -> return default | _, "y" -> return_true | _, "n" -> return_false | _, "q" -> failwith "Exit by user request." | _ -> get_boolean_answer cctxt ~msg ~default in let* email = cctxt#prompt "Enter the e-mail used for the paper wallet: " in let rec loop_words acc i = if i > 14 then return (List.rev acc) else let* word = cctxt#prompt_password "Enter word %d: " i in match Bip39.index_of_word (Bytes.to_string word) with | None -> loop_words acc i | Some wordidx -> loop_words (wordidx :: acc) (succ i) in let* words = loop_words [] 0 in match Bip39.of_indices words with | None -> assert false | Some t -> ( let* password = cctxt#prompt_password "Enter the password used for the paper wallet: " in (* TODO: unicode normalization (NFKD)... *) let passphrase = Bytes.(cat (of_string email) password) in let sk = Bip39.to_seed ~passphrase t in let sk = Bytes.sub sk 0 32 in let sk : Signature.Secret_key.t = Ed25519 (Data_encoding.Binary.of_bytes_exn Signature.Ed25519.Secret_key.encoding sk) in let pk = Signature.Secret_key.to_public_key sk in let pkh = Signature.Public_key.hash pk in let msg = Format.asprintf "Your public Tezos address is %a is that correct?" Signature.Public_key_hash.pp pkh in let* b = get_boolean_answer cctxt ~msg ~default:true in match b with true -> return sk | false -> input_fundraiser_params cctxt) let fail_if_already_registered cctxt force pk_uri name = let open Lwt_result_syntax in let* o = Public_key.find_opt cctxt name in match o with | None -> return_unit | Some (pk_uri_found, _) -> fail_unless (pk_uri = pk_uri_found || force) (error_of_fmt "public and secret keys '%s' don't correspond, please don't use \ --force" name) let keys_count_param = let open Tezos_clic in param ~name:"keys_count" ~desc:"How many keys to generate" (parameter (fun _ s -> let open Lwt_result_syntax in match int_of_string_opt s with | None -> failwith "number of keys must be an integer" | Some x -> if x < 0 then failwith "number of keys must be positive" else return x)) (** The kind of info that the [generate_test_keys] command outputs. *) type source = { pkh : Signature.public_key_hash; pk : Signature.public_key; sk : Signature.secret_key; } let source_encoding = let open Data_encoding in conv (fun {pkh; pk; sk} -> (pkh, pk, sk)) (fun (pkh, pk, sk) -> {pkh; pk; sk}) (obj3 (req "pkh" Signature.Public_key_hash.encoding) (req "pk" Signature.Public_key.encoding) (req "sk" Signature.Secret_key.encoding)) let source_list_encoding = Data_encoding.list source_encoding (* Simple helpers used to manage wallet files a raw way. *) module Wallet_helpers = struct let write_file path str = let open Lwt_syntax in let* fd = Lwt_unix.openfile path Unix.[O_CREAT; O_TRUNC; O_RDWR] 0o644 in let* _written_bytes = Lwt.catch (fun () -> Lwt_unix.write_string fd str 0 (String.length str)) (fun exn -> let* () = Lwt_unix.close fd in Lwt.fail exn) in Lwt_unix.close fd module Aliases = struct let encoding = list (obj1 (req "alias" Data_encoding.string)) let name = "aliases" end end (** Generate an array of accounts for testing purposes, store them into a wallet and output them to stdout in the JSON format. It is essential that this command lives here and not in the protocol-specific code because it should be available before a protocol is activated. *) let generate_test_keys = let open Tezos_clic in let alias_prefix_param = arg ~long:"alias-prefix" ~placeholder:"PREFIX" ~doc: "use a custom alias prefix (default: bootstrap). Keys will be \ generated with alias \"PREFIX<ID>\" where ID is unique for all key" (parameter (fun _ s -> Lwt_result_syntax.return s)) in command ~group ~desc:"Generate an array of accounts for testing purposes." (args1 alias_prefix_param) (prefixes ["stresstest"; "gen"; "keys"] @@ keys_count_param @@ stop) (fun alias_prefix n (cctxt : Client_context.full) -> let open Lwt_result_syntax in (* By default, the alias prefix matches the bootstrap<idx> pattern used in sandboxed mode.*) let alias_prefix = match alias_prefix with | None -> fun i -> Format.sprintf "bootstrap%d" (i + 6) | Some alias_prefix -> Format.sprintf "%s%06d" alias_prefix in let* source_list = List.init_es ~when_negative_length:[] n (fun i -> let alias = alias_prefix i in let pkh, pk, sk = Signature.generate_key ~algo:Signature.Ed25519 () in let*? pk_uri = Tezos_signer_backends.Unencrypted.make_pk pk in let*? sk_uri = Tezos_signer_backends.Unencrypted.make_sk sk in return ({pkh; pk; sk}, pk_uri, sk_uri, alias)) in (* All keys are registered into a single wallet. *) let* () = register_keys cctxt (List.rev_map (fun (x, pk_uri, sk_uri, alias) -> (alias, x.pkh, x.pk, pk_uri, sk_uri)) source_list) in (* Extract and write wallet aliases *) let aliases = List.rev_map (fun (_, _, _, alias) -> alias) source_list in let wallet_path = cctxt#get_base_dir in let*! () = Wallet_helpers.( write_file (Filename.concat wallet_path Aliases.name) Data_encoding.Json.(to_string (construct Aliases.encoding aliases))) in let json = Data_encoding.Json.construct source_list_encoding (List.map (fun (x, _, _, _) -> x) source_list) in let*! () = cctxt#message "%a@." Data_encoding.Json.pp json in return_unit) let aggregate_fail_if_already_registered cctxt force pk_uri name = let open Lwt_result_syntax in let* pk_opt = Aggregate_alias.Public_key.find_opt cctxt name in match pk_opt with | None -> return_unit | Some (pk_uri_found, _) -> fail_unless (pk_uri = pk_uri_found || force) (error_of_fmt "public and secret keys '%s' don't correspond, please don't use \ --force" name) module Bls_commands = struct open Lwt_result_syntax let generate_keys ~force ~encrypted name (cctxt : #Client_context.io_wallet) = let* name = Aggregate_alias.Secret_key.of_fresh cctxt force name in let mnemonic = Mnemonic.new_random in let*! () = cctxt#message "It is important to save this mnemonic in a secure place:@\n\ @\n\ %a@\n\ @\n\ The mnemonic can be used to recover your spending key.@." Mnemonic.words_pp (Bip39.to_words mnemonic) in let seed = Mnemonic.to_32_bytes mnemonic in let pkh, pk, sk = Tezos_crypto.Aggregate_signature.generate_key ~seed () in let*? pk_uri = Tezos_signer_backends.Unencrypted.Aggregate.make_pk pk in let* sk_uri = if encrypted then Tezos_signer_backends.Encrypted.prompt_twice_and_encrypt_aggregate cctxt sk else Tezos_signer_backends.Unencrypted.Aggregate.make_sk sk |> Lwt.return in register_aggregate_key cctxt ~force (pkh, pk_uri, sk_uri) ~public_key:pk name let list_keys (cctxt : #Client_context.io_wallet) = let* aggregate_keys_list = list_aggregate_keys cctxt in List.iter_es (fun (name, pkh, pk, sk) -> let* pkh_str = Aggregate_alias.Public_key_hash.to_source pkh in let*! () = match (pk, sk) with | None, None -> cctxt#message "%s: %s" name pkh_str | _, Some uri -> let scheme = Option.value ~default:"aggregate_unencrypted" @@ Uri.scheme (uri : aggregate_sk_uri :> Uri.t) in cctxt#message "%s: %s (%s sk known)" name pkh_str scheme | Some _, _ -> cctxt#message "%s: %s (pk known)" name pkh_str in return_unit) aggregate_keys_list let show_address ~show_private name (cctxt : #Client_context.io_wallet) = let* keys_opt = alias_aggregate_keys cctxt name in match keys_opt with | None -> let*! () = cctxt#error "No keys found for address" in return_unit | Some (pkh, pk, skloc) -> ( let*! () = cctxt#message "Hash: %a" Tezos_crypto.Aggregate_signature.Public_key_hash.pp pkh in match pk with | None -> return_unit | Some pk -> let*! () = cctxt#message "Public Key: %a" Tezos_crypto.Aggregate_signature.Public_key.pp pk in if show_private then Option.iter_es (fun skloc -> let* skloc = Aggregate_alias.Secret_key.to_source skloc in let*! () = cctxt#message "Secret Key: %s" skloc in return_unit) skloc else return_unit) let import_secret_key ~force name sk_uri (cctxt : #Client_context.io_wallet) = let* name = Aggregate_alias.Secret_key.of_fresh cctxt false name in let* pk_uri = aggregate_neuterize sk_uri in let* () = aggregate_fail_if_already_registered cctxt force pk_uri name in let* pkh, public_key = import_aggregate_secret_key ~io:(cctxt :> Client_context.io_wallet) pk_uri in let*! () = cctxt#message "Bls address added: %a" Tezos_crypto.Aggregate_signature.Public_key_hash.pp pkh in register_aggregate_key cctxt (pkh, pk_uri, sk_uri) ?public_key name end let commands network : Client_context.full Tezos_clic.command list = let open Lwt_result_syntax in let open Tezos_clic in let encrypted_switch () = if List.exists (fun (scheme, _) -> scheme = Tezos_signer_backends.Unencrypted.scheme) (Client_keys.registered_signers ()) then Tezos_clic.switch ~long:"encrypted" ~doc:"Encrypt the key on-disk" () else Tezos_clic.constant true in let show_private_switch = switch ~long:"show-secret" ~short:'S' ~doc:"show the private key" () in [ generate_test_keys; command ~group ~desc: "List supported signing schemes.\n\ Signing schemes are identifiers for signer modules: the built-in \ signing routines, a hardware wallet, an external agent, etc.\n\ Each signer has its own format for describing secret keys, such a raw \ secret key for the default `unencrypted` scheme, the path on a \ hardware security module, an alias for an external agent, etc.\n\ This command gives the list of signer modules that this version of \ the tezos client supports." no_options (fixed ["list"; "signing"; "schemes"]) (fun () (cctxt : Client_context.full) -> let signers = List.sort (fun (ka, _) (kb, _) -> String.compare ka kb) (registered_signers ()) in let*! () = List.iter_s (fun (n, signer) -> match signer with | Simple (module S : SIGNER) -> cctxt#message "@[<v 2>Scheme `%s`: %s@,@[<hov 0>%a@]@]" n S.title Format.pp_print_text S.description | Aggregate (module S : AGGREGATE_SIGNER) -> cctxt#message "@[<v 2>Aggregate scheme `%s`: %s@,@[<hov 0>%a@]@]" n S.title Format.pp_print_text S.description) signers in return_unit); (match network with | Some `Mainnet -> command ~group ~desc:"Generate a pair of keys." (args2 (Secret_key.force_switch ()) sig_algo_arg) (prefixes ["gen"; "keys"] @@ Secret_key.fresh_alias_param @@ stop) (fun (force, algo) name (cctxt : Client_context.full) -> let* name = Secret_key.of_fresh cctxt force name in let pkh, pk, sk = Signature.generate_key ~algo () in let*? pk_uri = Tezos_signer_backends.Unencrypted.make_pk pk in let* sk_uri = Tezos_signer_backends.Encrypted.prompt_twice_and_encrypt cctxt sk in register_key cctxt ~force (pkh, pk_uri, sk_uri) name) | Some `Testnet | None -> command ~group ~desc:"Generate a pair of keys." (args3 (Secret_key.force_switch ()) sig_algo_arg (encrypted_switch ())) (prefixes ["gen"; "keys"] @@ Secret_key.fresh_alias_param @@ stop) (fun (force, algo, encrypted) name (cctxt : Client_context.full) -> let* name = Secret_key.of_fresh cctxt force name in let pkh, pk, sk = Signature.generate_key ~algo () in let*? pk_uri = Tezos_signer_backends.Unencrypted.make_pk pk in let* sk_uri = if encrypted then Tezos_signer_backends.Encrypted.prompt_twice_and_encrypt cctxt sk else Lwt.return (Tezos_signer_backends.Unencrypted.make_sk sk) in register_key cctxt ~force (pkh, pk_uri, sk_uri) name)); (match network with | Some `Mainnet -> command ~group ~desc:"Generate keys including the given string." (args3 (switch ~long:"prefix" ~short:'P' ~doc:"the key must begin with tz1[word]" ()) (switch ~long:"ignore-case" ~short:'I' ~doc:"make the pattern case-insensitive" ()) (force_switch ())) (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") (fun (prefix, ignore_case, force) name containing (cctxt : Client_context.full) -> let* name = Public_key_hash.of_fresh cctxt force name in gen_keys_containing ~encrypted:true ~force ~prefix ~ignore_case ~containing ~name cctxt) | Some `Testnet | None -> command ~group ~desc:"Generate keys including the given string." (args4 (switch ~long:"prefix" ~short:'P' ~doc:"the key must begin with tz1[word]" ()) (switch ~long:"ignore-case" ~short:'I' ~doc:"make the pattern case-insensitive" ()) (force_switch ()) (encrypted_switch ())) (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") (fun (prefix, ignore_case, force, encrypted) name containing (cctxt : Client_context.full) -> let* name = Public_key_hash.of_fresh cctxt force name in gen_keys_containing ~encrypted ~force ~prefix ~ignore_case ~containing ~name cctxt)); command ~group ~desc:"Encrypt an unencrypted secret key." no_options (prefixes ["encrypt"; "secret"; "key"] @@ stop) (fun () (cctxt : Client_context.full) -> let* sk_uri = cctxt#prompt_password "Enter unencrypted secret key: " in let sk_uri = Uri.of_string (Bytes.to_string sk_uri) in let* () = match Uri.scheme sk_uri with | None | Some "unencrypted" -> return_unit | _ -> failwith "This command can only be used with the \"unencrypted\" scheme" in let* sk = Lwt.return (Signature.Secret_key.of_b58check (Uri.path sk_uri)) in let* sk_uri = Tezos_signer_backends.Encrypted.prompt_twice_and_encrypt cctxt sk in let*! () = cctxt#message "Encrypted secret key %a" Uri.pp_hum (sk_uri :> Uri.t) in return_unit); command ~group ~desc:"Add a secret key to the wallet." (args1 (Secret_key.force_switch ())) (prefix "import" @@ prefixes ["secret"; "key"] @@ Secret_key.fresh_alias_param @@ Client_keys.sk_uri_param @@ stop) (fun force name sk_uri (cctxt : Client_context.full) -> let* name = Secret_key.of_fresh cctxt force name in let* pk_uri = Client_keys.neuterize sk_uri in let* () = fail_if_already_registered cctxt force pk_uri name in let* pkh, public_key = Client_keys.import_secret_key ~io:(cctxt :> Client_context.io_wallet) pk_uri in let*! () = cctxt#message "Tezos address added: %a" Signature.Public_key_hash.pp pkh in register_key cctxt ~force (pkh, pk_uri, sk_uri) ?public_key name); ] @ (if network <> Some `Mainnet then [] else [ command ~group ~desc:"Add a fundraiser secret key to the wallet." (args1 (Secret_key.force_switch ())) (prefix "import" @@ prefixes ["fundraiser"; "secret"; "key"] @@ Secret_key.fresh_alias_param @@ stop) (fun force name (cctxt : Client_context.full) -> let* name = Secret_key.of_fresh cctxt force name in let* sk = input_fundraiser_params cctxt in let* sk_uri = Tezos_signer_backends.Encrypted.prompt_twice_and_encrypt cctxt sk in let* pk_uri = Client_keys.neuterize sk_uri in let* () = fail_if_already_registered cctxt force pk_uri name in let* pkh, _public_key = Client_keys.public_key_hash pk_uri in register_key cctxt ~force (pkh, pk_uri, sk_uri) name); ]) @ [ command ~group ~desc:"Add a public key to the wallet." (args1 (Public_key.force_switch ())) (prefix "import" @@ prefixes ["public"; "key"] @@ Public_key.fresh_alias_param @@ Client_keys.pk_uri_param @@ stop) (fun force name pk_uri (cctxt : Client_context.full) -> let* name = Public_key.of_fresh cctxt force name in let* pkh, public_key = Client_keys.public_key_hash pk_uri in let* () = Public_key_hash.add ~force cctxt name pkh in let*! () = cctxt#message "Tezos address added: %a" Signature.Public_key_hash.pp pkh in Public_key.add ~force cctxt name (pk_uri, public_key)); command ~group ~desc:"Add an address to the wallet." (args1 (Public_key.force_switch ())) (prefixes ["add"; "address"] @@ Public_key_hash.fresh_alias_param @@ Public_key_hash.source_param @@ stop) (fun force name hash cctxt -> let* name = Public_key_hash.of_fresh cctxt force name in Public_key_hash.add ~force cctxt name hash); command ~group ~desc:"List all addresses and associated keys." no_options (fixed ["list"; "known"; "addresses"]) (fun () (cctxt : #Client_context.full) -> let* l = list_keys cctxt in List.iter_es (fun (name, pkh, pk, sk) -> let* v = Public_key_hash.to_source pkh in let*! () = match (pk, sk) with | None, None -> cctxt#message "%s: %s" name v | _, Some uri -> let scheme = Option.value ~default:"unencrypted" @@ Uri.scheme (uri : sk_uri :> Uri.t) in cctxt#message "%s: %s (%s sk known)" name v scheme | Some _, _ -> cctxt#message "%s: %s (pk known)" name v in return_unit) l); command ~group ~desc:"Show the keys associated with an implicit account." (args1 show_private_switch) (prefixes ["show"; "address"] @@ Public_key_hash.alias_param @@ stop) (fun show_private (name, _) (cctxt : #Client_context.full) -> let* key_info = alias_keys cctxt name in match key_info with | None -> let*! () = cctxt#error "No keys found for address" in return_unit | Some (pkh, pk, skloc) -> ( let*! () = cctxt#message "Hash: %a" Signature.Public_key_hash.pp pkh in match pk with | None -> return_unit | Some pk -> let*! () = cctxt#message "Public Key: %a" Signature.Public_key.pp pk in if show_private then match skloc with | None -> return_unit | Some skloc -> let* skloc = Secret_key.to_source skloc in let*! () = cctxt#message "Secret Key: %s" skloc in return_unit else return_unit)); command ~group ~desc:"Forget one address." (args1 (Tezos_clic.switch ~long:"force" ~short:'f' ~doc:"delete associated keys when present" ())) (prefixes ["forget"; "address"] @@ Public_key_hash.alias_param @@ stop) (fun force (name, _pkh) (cctxt : Client_context.full) -> let* has_secret_key = Secret_key.mem cctxt name in let* has_public_key = Public_key.mem cctxt name in let* () = fail_when ((not force) && (has_secret_key || has_public_key)) (error_of_fmt "secret or public key present for %s, use --force to delete" name) in let* () = Secret_key.del cctxt name in let* () = Public_key.del cctxt name in Public_key_hash.del cctxt name); command ~group ~desc:"Forget the entire wallet of keys." (args1 (Tezos_clic.switch ~long:"force" ~short:'f' ~doc:"you got to use the force for that" ())) (fixed ["forget"; "all"; "keys"]) (fun force (cctxt : Client_context.full) -> let* () = fail_unless force (error_of_fmt "this can only be used with option --force") in let* () = Public_key.set cctxt [] in let* () = Secret_key.set cctxt [] in Public_key_hash.set cctxt []); command ~group ~desc:"Compute deterministic nonce." no_options (prefixes ["generate"; "nonce"; "for"] @@ Public_key_hash.alias_param @@ prefixes ["from"] @@ string ~name:"data" ~desc:"string from which to deterministically generate the nonce" @@ stop) (fun () (name, _pkh) data (cctxt : Client_context.full) -> let data = Bytes.of_string data in let* sk_present = Secret_key.mem cctxt name in let* () = fail_unless sk_present (error_of_fmt "secret key not present for %s" name) in let* sk_uri = Secret_key.find cctxt name in let* nonce = Client_keys.deterministic_nonce sk_uri data in let*! () = cctxt#message "%a" Hex.pp (Hex.of_bytes nonce) in return_unit); command ~group ~desc:"Compute deterministic nonce hash." no_options (prefixes ["generate"; "nonce"; "hash"; "for"] @@ Public_key_hash.alias_param @@ prefixes ["from"] @@ string ~name:"data" ~desc: "string from which to deterministically generate the nonce hash" @@ stop) (fun () (name, _pkh) data (cctxt : Client_context.full) -> let data = Bytes.of_string data in let* sk_present = Secret_key.mem cctxt name in let* () = fail_unless sk_present (error_of_fmt "secret key not present for %s" name) in let* sk_uri = Secret_key.find cctxt name in let* nonce_hash = Client_keys.deterministic_nonce_hash sk_uri data in let*! () = cctxt#message "%a" Hex.pp (Hex.of_bytes nonce_hash) in return_unit); command ~group ~desc: "Import a pair of keys to the wallet from a mnemonic phrase. This \ command uses the BIP39 algorithm, and therefore imports \ public/secret keys that may be different from a Ledger application, \ depending on the BIP32 derivation path used in the Ledger. This \ command also uses the Ed25519 algorithm, which means it generates \ tz1 public key hashes." (args2 (Secret_key.force_switch ()) (switch ~doc:"encrypt the secret key" ~long:"encrypt" ())) (prefix "import" @@ prefixes ["keys"; "from"; "mnemonic"] @@ Secret_key.fresh_alias_param @@ stop) (fun (force, encrypt) name (cctxt : Client_context.full) -> let* name = Secret_key.of_fresh cctxt force name in let* mnemonic = cctxt#prompt "Enter your mnemonic: " in let mnemonic = String.trim mnemonic |> String.split_on_char ' ' in match Bip39.of_words mnemonic with | None -> failwith "\"%s\" is not a valid BIP39 mnemonic. Please ensure that your \ mnemonic is of correct length, and that each word is \ separated by a single space. For reference, a correct \ mnemonic is comprised of 12, 15, 18, 21, or 24 words where \ the last is a checksum. Do not try to write your own \ mnemonic." (String.concat " " mnemonic) | Some t -> let* passphrase = cctxt#prompt_password "Enter your passphrase: " in let sk = Bip39.to_seed ~passphrase t in let sk = Bytes.sub sk 0 32 in let sk : Signature.Secret_key.t = Ed25519 (Data_encoding.Binary.of_bytes_exn Signature.Ed25519.Secret_key.encoding sk) in let*? unencrypted_sk_uri = Tezos_signer_backends.Unencrypted.make_sk sk in let* sk_uri = match encrypt with | true -> Tezos_signer_backends.Encrypted.prompt_twice_and_encrypt cctxt sk | false -> return unencrypted_sk_uri in let* pk_uri = neuterize unencrypted_sk_uri in let* () = fail_if_already_registered cctxt force pk_uri name in let* pkh, public_key = import_secret_key ~io:(cctxt :> Client_context.io_wallet) pk_uri in let* () = register_key cctxt ~force (pkh, pk_uri, sk_uri) ?public_key name in let*! () = cctxt#message "Tezos address added: %a" Signature.Public_key_hash.pp pkh in return_unit); (let desc = "Generate a pair of BLS keys." in let force_switch = Aggregate_alias.Secret_key.force_switch in let cmd = prefixes ["bls"; "gen"; "keys"] @@ Aggregate_alias.Secret_key.fresh_alias_param @@ stop in match network with | Some `Mainnet -> command ~group ~desc (args1 (force_switch ())) cmd (fun force name (cctxt : #Client_context.full) -> Bls_commands.generate_keys ~force ~encrypted:true name cctxt) | Some `Testnet | None -> command ~group ~desc (args2 (force_switch ()) (encrypted_switch ())) cmd (fun (force, encrypted) name (cctxt : #Client_context.full) -> Bls_commands.generate_keys ~force ~encrypted name cctxt)); command ~group ~desc:"List BlS keys." no_options (prefixes ["bls"; "list"; "keys"] @@ stop) (fun () cctxt -> Bls_commands.list_keys cctxt); command ~group ~desc:"Show the keys associated with an rollup account." (args1 show_private_switch) (prefixes ["bls"; "show"; "address"] @@ Aggregate_alias.Public_key_hash.alias_param @@ stop) (fun show_private (name, _pkh) (cctxt : #Client_context.full) -> Bls_commands.show_address ~show_private name cctxt); command ~group ~desc:"Add a secret key to the wallet." (args1 (Aggregate_alias.Secret_key.force_switch ())) (prefixes ["bls"; "import"; "secret"; "key"] @@ Aggregate_alias.Secret_key.fresh_alias_param @@ aggregate_sk_uri_param @@ stop) (fun force name sk_uri cctxt -> Bls_commands.import_secret_key ~force name sk_uri cctxt); ]