diff --git a/src/bin_client/test/jbuild b/src/bin_client/test/jbuild index 591d31532..2f39f403d 100644 --- a/src/bin_client/test/jbuild +++ b/src/bin_client/test/jbuild @@ -4,6 +4,8 @@ ((name runtest_basic.sh) (deps (sandbox.json protocol_parameters.json + king_commitment.json + queen_commitment.json test_lib.inc.sh (glob_files contracts/*) )) diff --git a/src/bin_client/test/king_commitment.json b/src/bin_client/test/king_commitment.json new file mode 100644 index 000000000..af963b3c6 --- /dev/null +++ b/src/bin_client/test/king_commitment.json @@ -0,0 +1,8 @@ + { + "mnemonic": ["envelope", "hospital", "mind", "sunset", "cancel", "muscle", "leisure", "thumb", "wine", "market", "exit", "lucky", "style", "picnic", "success"], + "secret": "0f39ed0b656509c2ecec4771712d9cddefe2afac", + "amount": "23932454669343", + "pkh": "tz1MawerETND6bqJqx8GV3YHUrvMBCDasRBF", + "password": "z0eZHQQGKt", + "email": "cjgfoqmk.wpxnvnup@tezos.example.org" +} diff --git a/src/bin_client/test/queen_commitment.json b/src/bin_client/test/queen_commitment.json new file mode 100644 index 000000000..0534e38cf --- /dev/null +++ b/src/bin_client/test/queen_commitment.json @@ -0,0 +1,8 @@ + { + "mnemonic": ["flag", "quote", "will", "valley", "mouse", "chat", "hold", "prosper", "silk", "tent", "cruel", "cause", "demise", "bottom", "practice"], + "secret": "41f98b15efc63fa893d61d7d6eee4a2ce9427ac4", + "amount": "72954577464032", + "pkh": "tz1X4maqF9tC1Yn4jULjHRAyzjAtc25Z68TX", + "password": "MHErskWPE6", + "email": "oklmcktr.ztljnpzc@tezos.example.org" + } diff --git a/src/bin_client/test/test_basic.sh b/src/bin_client/test/test_basic.sh index 7eed0ece7..566606689 100755 --- a/src/bin_client/test/test_basic.sh +++ b/src/bin_client/test/test_basic.sh @@ -26,6 +26,8 @@ sleep 1 key1=foo key2=bar key3=boo +key4=king +key5=queen $client gen keys $key1 $client gen keys $key2 --sig secp256k1 @@ -106,6 +108,20 @@ $client transfer 400,000 from bootstrap1 to bootstrap5 -fee 0 sleep 1 $client bake for bootstrap1 -max-priority 512 $client get balance for bootstrap5 | assert "4,000,000 ꜩ" +sleep 1 + + +$client activate account $key4 with king_commitment.json --no-confirmation +$client activate account $key5 with queen_commitment.json --no-confirmation +$client bake for bootstrap1 -max-priority 512 +sleep 1 + +$client get balance for $key4 | assert "23,932,454.669,343 ꜩ" +$client get balance for $key5 | assert "72,954,577.464,032 ꜩ" + +$client transfer 10 from $key4 to $key5 + + echo echo End of test diff --git a/src/lib_client_base/client_keys.ml b/src/lib_client_base/client_keys.ml index 1b1605ce9..b96e293eb 100644 --- a/src/lib_client_base/client_keys.ml +++ b/src/lib_client_base/client_keys.ml @@ -159,9 +159,8 @@ let append cctxt loc buf = sign cctxt loc buf >>|? fun signature -> Signature.concat buf signature -let gen_keys ?(force=false) ?algo ?seed (cctxt : #Client_context.io_wallet) name = - let public_key_hash, public_key, secret_key = - Signature.generate_key ?algo ?seed () in +let register_key cctxt ?(force=false) + (public_key_hash, public_key, secret_key) name = Secret_key.add ~force cctxt name (Secret_key_locator.of_unencrypted secret_key) >>=? fun () -> Public_key.add ~force cctxt name @@ -170,6 +169,9 @@ let gen_keys ?(force=false) ?algo ?seed (cctxt : #Client_context.io_wallet) name cctxt name public_key_hash >>=? fun () -> return () +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 let gen_keys_containing ?(prefix=false) ?(force=false) ~containing ~name (cctxt : #Client_context.full) = let unrepresentable = diff --git a/src/lib_client_base/client_keys.mli b/src/lib_client_base/client_keys.mli index a9e375dc9..54214891c 100644 --- a/src/lib_client_base/client_keys.mli +++ b/src/lib_client_base/client_keys.mli @@ -116,6 +116,13 @@ val gen_keys : ?seed:Ed25519.Seed.t -> #Client_context.io_wallet -> string -> unit tzresult Lwt.t +val register_key : + #Client_context.wallet -> + ?force:bool -> + (Signature.Public_key_hash.t * + Signature.Public_key.t * + Signature.Secret_key.t) -> string -> unit tzresult Lwt.t + val gen_keys_containing : ?prefix:bool -> ?force:bool -> diff --git a/src/proto_alpha/lib_client/client_proto_args.ml b/src/proto_alpha/lib_client/client_proto_args.ml index 29e400e37..b4caa0265 100644 --- a/src/proto_alpha/lib_client/client_proto_args.ml +++ b/src/proto_alpha/lib_client/client_proto_args.ml @@ -180,6 +180,12 @@ let no_print_source_flag = This option disables this behaviour." () +let no_confirmation = + switch + ~long:"no-confirmation" + ~doc:"don't print wait for the operation to be confirmed." + () + module Daemon = struct let baking_switch = switch diff --git a/src/proto_alpha/lib_client/client_proto_args.mli b/src/proto_alpha/lib_client/client_proto_args.mli index c5dd00564..26a8f9cfb 100644 --- a/src/proto_alpha/lib_client/client_proto_args.mli +++ b/src/proto_alpha/lib_client/client_proto_args.mli @@ -25,7 +25,8 @@ val free_baking_switch: (bool, Proto_alpha.full) Clic.arg val force_switch: (bool, Proto_alpha.full) Clic.arg val endorsement_delay_arg: (int, Proto_alpha.full) Clic.arg -val no_print_source_flag : (bool, Proto_alpha.full) Clic.arg +val no_print_source_flag: (bool, Proto_alpha.full) Clic.arg +val no_confirmation: (bool, Proto_alpha.full) Clic.arg val tez_arg : default:string -> diff --git a/src/proto_alpha/lib_client/client_proto_context.ml b/src/proto_alpha/lib_client/client_proto_context.ml index 626701415..d4875018d 100644 --- a/src/proto_alpha/lib_client/client_proto_context.ml +++ b/src/proto_alpha/lib_client/client_proto_context.ml @@ -285,3 +285,77 @@ let wait_for_operation_inclusion end stream >>= fun _ -> stop () ; return () + +type activation_key = + { pkh : Ed25519.Public_key_hash.t ; + amount : Tez.t ; + secret : Blinded_public_key_hash.secret ; + mnemonic : string list ; + password : string ; + email : string ; + } + +let activation_key_encoding = + let open Data_encoding in + conv + (fun { pkh ; amount ; secret ; mnemonic ; password ; email } -> + ( pkh, amount, secret, mnemonic, password, email )) + (fun ( pkh, amount, secret, mnemonic, password, email ) -> + { pkh ; amount ; secret ; mnemonic ; password ; email }) + (obj6 + (req "pkh" Ed25519.Public_key_hash.encoding) + (req "amount" Tez.encoding) + (req "secret" Blinded_public_key_hash.secret_encoding) + (req "mnemonic" (list string)) + (req "password" string) + (req "email" string)) + +let read_key key = + match Bip39.of_words key.mnemonic with + | None -> + failwith "" + | Some t -> + (* TODO: unicode normalization (NFKD)... *) + let sk = Bip39.to_seed ~passphrase:(key.email ^ key.password) t in + let sk = Cstruct.(to_bigarray (sub sk 0 32)) in + let sk : Signature.Secret_key.t = + Ed25519 (Data_encoding.Binary.of_bytes_exn Ed25519.Secret_key.encoding sk) in + let pk = Signature.Secret_key.to_public_key sk in + let pkh = Signature.Public_key.hash pk in + return (pkh, pk, sk) + +let claim_commitment (cctxt : #Proto_alpha.full) + ?confirmations ?force block key name = + read_key key >>=? fun (pkh, pk, sk) -> + fail_unless (Signature.Public_key_hash.equal pkh (Ed25519 key.pkh)) + (failure "@[Inconsistent activation key:@ \ + Computed pkh: %a@ \ + Embedded pkh: %a @]" + Signature.Public_key_hash.pp pkh + Ed25519.Public_key_hash.pp key.pkh) >>=? fun () -> + let op = [ Activation { id = key.pkh ; secret = key.secret } ] in + Block_services.info cctxt block >>=? fun bi -> + Alpha_services.Forge.Anonymous.operations + cctxt block ~branch:bi.hash op >>=? fun bytes -> + Shell_services.inject_operation + cctxt ~chain_id:bi.chain_id bytes >>=? fun oph -> + operation_submitted_message cctxt oph >>=? fun () -> + begin + match confirmations with + | None -> + Client_keys.register_key cctxt ?force (pkh, pk, sk) name >>=? fun () -> + return () + | Some confirmations -> + cctxt#message "Waiting for the operation to be included..." >>= fun () -> + wait_for_operation_inclusion ~confirmations cctxt oph >>=? fun () -> + Client_keys.register_key cctxt ?force (pkh, pk, sk) name >>=? fun () -> + Alpha_services.Contract.balance + cctxt (`Head 0) (Contract.implicit_contract pkh) >>=? fun balance -> + cctxt#message "Account %s (%a) created with %s%a." + name + Signature.Public_key_hash.pp pkh + Client_proto_args.tez_sym + Tez.pp balance >>= fun () -> + return () + end + diff --git a/src/proto_alpha/lib_client/client_proto_context.mli b/src/proto_alpha/lib_client/client_proto_context.mli index d5d3d1a74..4864fcc77 100644 --- a/src/proto_alpha/lib_client/client_proto_context.mli +++ b/src/proto_alpha/lib_client/client_proto_context.mli @@ -141,3 +141,24 @@ val wait_for_operation_inclusion: ?confirmations:int -> Operation_hash.t -> unit tzresult Lwt.t + +type activation_key = + { pkh : Ed25519.Public_key_hash.t ; + amount : Tez.t ; + secret : Blinded_public_key_hash.secret ; + mnemonic : string list ; + password : string ; + email : string ; + } + +val activation_key_encoding: activation_key Data_encoding.t + +val claim_commitment: + #Proto_alpha.full -> + ?confirmations:int -> + ?force:bool -> + Block_services.block -> + activation_key -> + string -> + unit tzresult Lwt.t + diff --git a/src/proto_alpha/lib_client/jbuild b/src/proto_alpha/lib_client/jbuild index 478071476..789463172 100644 --- a/src/proto_alpha/lib_client/jbuild +++ b/src/proto_alpha/lib_client/jbuild @@ -8,7 +8,8 @@ tezos-protocol-environment tezos-shell-services tezos-client-base - tezos-rpc)) + tezos-rpc + bip39)) (library_flags (:standard -linkall)) (flags (:standard -w -9+27-30-32-40@8 -safe-string diff --git a/src/proto_alpha/lib_client_commands/client_proto_context_commands.ml b/src/proto_alpha/lib_client_commands/client_proto_context_commands.ml index 916f71b53..a11f621f5 100644 --- a/src/proto_alpha/lib_client_commands/client_proto_context_commands.ml +++ b/src/proto_alpha/lib_client_commands/client_proto_context_commands.ml @@ -28,6 +28,13 @@ let report_michelson_errors ?(no_print_source=false) ~msg (cctxt : #Client_conte | Ok data -> Lwt.return (Some data) +let file_parameter = + Clic.parameter (fun _ p -> + if not (Sys.file_exists p) then + failwith "File doesn't exist: '%s'" p + else + return p) + let group = { Clic.name = "context" ; title = "Block contextual commands (see option -block)" } @@ -253,6 +260,32 @@ let commands () = operation_submitted_message cctxt oph end; + command ~group ~desc:"Register and activate a predefined account using the provided activation key." + (args2 (Secret_key.force_switch ()) (Client_proto_args.no_confirmation)) + (prefixes [ "activate" ; "account" ] + @@ Secret_key.fresh_alias_param + @@ prefixes [ "with" ] + @@ param ~name:"activation_key" + ~desc:"Activation key (as JSON file) obtained from the Tezos foundation (or the Alphanet faucet)." + file_parameter + @@ stop) + (fun (force, no_confirmation) name activation_key_file cctxt -> + Secret_key.of_fresh cctxt force name >>=? fun name -> + Lwt_utils_unix.Json.read_file activation_key_file >>=? fun json -> + match Data_encoding.Json.destruct + Client_proto_context.activation_key_encoding + json with + | exception (Data_encoding.Json.Cannot_destruct _ as exn) -> + Format.kasprintf (fun s -> failwith "%s" s) + "Invalid activation file: %a %a" + (fun ppf -> Data_encoding.Json.print_error ppf) exn + Data_encoding.Json.pp json + | key -> + let confirmations = + if no_confirmation then None else Some 0 in + claim_commitment cctxt cctxt#block ?confirmations ~force key name + ); + command ~group:alphanet ~desc: "Activate a protocol (Alphanet dictator only)." no_options (prefixes [ "activate" ; "protocol" ] @@ -306,6 +339,7 @@ let commands () = wait_for_operation_inclusion ctxt ~confirmations ~predecessors operation_hash end ; + command ~group:alphanet ~desc: "Fork a test protocol (Alphanet dictator only)." no_options (prefixes [ "fork" ; "test" ; "protocol" ] diff --git a/src/proto_alpha/lib_client_commands/jbuild b/src/proto_alpha/lib_client_commands/jbuild index 385e5f7ee..d5d4869c5 100644 --- a/src/proto_alpha/lib_client_commands/jbuild +++ b/src/proto_alpha/lib_client_commands/jbuild @@ -4,6 +4,7 @@ ((name tezos_client_alpha_commands) (public_name tezos-client-alpha-commands) (libraries (tezos-base + tezos-stdlib-unix tezos-protocol-alpha tezos-protocol-environment tezos-shell-services @@ -16,6 +17,7 @@ (flags (:standard -w -9+27-30-32-40@8 -safe-string -open Tezos_base__TzPervasives + -open Tezos_stdlib_unix -open Tezos_shell_services -open Tezos_client_base -open Tezos_client_alpha diff --git a/src/proto_alpha/lib_protocol/src/apply.ml b/src/proto_alpha/lib_protocol/src/apply.ml index 92d892d57..56e29b173 100644 --- a/src/proto_alpha/lib_protocol/src/apply.ml +++ b/src/proto_alpha/lib_protocol/src/apply.ml @@ -36,7 +36,7 @@ type error += Too_early_double_baking_evidence of { level: Raw_level.t ; current: Raw_level.t } (* `Temporary *) type error += Outdated_double_baking_evidence of { level: Raw_level.t ; last: Raw_level.t } (* `Permanent *) -type error += Invalid_activation +type error += Invalid_activation of { pkh : Ed25519.Public_key_hash.t } type error += Wrong_activation_secret let () = @@ -295,11 +295,14 @@ let () = ~title:"Invalid activation" ~description:"The given key has already been activated or the given \ key does not correspond to any preallocated contract" - ~pp:(fun ppf () -> - Format.fprintf ppf "Invalid activation.") - Data_encoding.unit - (function Invalid_activation -> Some () | _ -> None) - (fun () -> Invalid_activation) ; + ~pp:(fun ppf pkh -> + Format.fprintf ppf "Invalid activation. The public key %a does \ + not match any commitment." + Ed25519.Public_key_hash.pp pkh + ) + Data_encoding.(obj1 (req "pkh" Ed25519.Public_key_hash.encoding)) + (function Invalid_activation { pkh } -> Some pkh | _ -> None) + (fun pkh -> Invalid_activation { pkh } ) ; register_error_kind `Permanent ~id:"operation.wrong_activation_secret" @@ -579,9 +582,9 @@ let apply_anonymous_operation ctxt _delegate origination_nonce kind = | Activation { id = pkh ; secret } -> let h_pkh = Unclaimed_public_key_hash.of_ed25519_pkh pkh in Commitment.get_opt ctxt h_pkh >>=? function - | None -> fail Invalid_activation - | Some { blinded_public_key_hash = submitted_bpkh ; amount } -> - let blinded_pkh = Blinded_public_key_hash.of_ed25519_pkh secret pkh in + | None -> fail (Invalid_activation { pkh }) + | Some { blinded_public_key_hash = blinded_pkh ; amount } -> + let submitted_bpkh = Blinded_public_key_hash.of_ed25519_pkh secret pkh in fail_unless Blinded_public_key_hash.(blinded_pkh = submitted_bpkh) Wrong_activation_secret >>=? fun () ->