From c56f4fa808c218e7e69837596d31aa9e444666a8 Mon Sep 17 00:00:00 2001
From: Martin Holst Swende <martin@swende.se>
Date: Tue, 31 Mar 2020 12:03:48 +0200
Subject: [PATCH] cmd/clef: add newaccount command (#20782)

* cmd/clef: add newaccount command

* cmd/clef: document clef_New, update API versioning

* Update cmd/clef/intapi_changelog.md

Co-Authored-By: ligi <ligi@ligi.de>

* Update signer/core/uiapi.go

Co-Authored-By: ligi <ligi@ligi.de>

Co-authored-by: ligi <ligi@ligi.de>
---
 cmd/clef/intapi_changelog.md | 11 +++++++++
 cmd/clef/main.go             | 48 ++++++++++++++++++++++++++++++++++--
 signer/core/api.go           | 14 ++++++++---
 signer/core/uiapi.go         | 10 ++++++++
 4 files changed, 78 insertions(+), 5 deletions(-)

diff --git a/cmd/clef/intapi_changelog.md b/cmd/clef/intapi_changelog.md
index 38424f06b..f7e6993cf 100644
--- a/cmd/clef/intapi_changelog.md
+++ b/cmd/clef/intapi_changelog.md
@@ -10,6 +10,17 @@ TL;DR: Given a version number MAJOR.MINOR.PATCH, increment the:
 
 Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.
 
+### 7.0.1 
+
+Added `clef_New` to the internal API calleable from a UI. 
+
+> `New` creates a new password protected Account. The private key is protected with
+> the given password. Users are responsible to backup the private key that is stored
+> in the keystore location that was specified when this API was created.
+> This method is the same as New on the external API, the difference being that
+> this implementation does not ask for confirmation, since it's initiated by
+> the user
+
 ### 7.0.0
 
 - The `message` field was renamed to `messages` in all data signing request methods to better reflect that it's a list, not a value.
diff --git a/cmd/clef/main.go b/cmd/clef/main.go
index dd898871d..f4533a6e8 100644
--- a/cmd/clef/main.go
+++ b/cmd/clef/main.go
@@ -187,6 +187,21 @@ The setpw command stores a password for a given address (keyfile).
 		Description: `
 The delpw command removes a password for a given address (keyfile).
 `}
+	newAccountCommand = cli.Command{
+		Action:    utils.MigrateFlags(newAccount),
+		Name:      "newaccount",
+		Usage:     "Create a new account",
+		ArgsUsage: "",
+		Flags: []cli.Flag{
+			logLevelFlag,
+			keystoreFlag,
+			utils.LightKDFFlag,
+		},
+		Description: `
+The newaccount command creates a new keystore-backed account. It is a convenience-method
+which can be used in lieu of an external UI.`,
+	}
+
 	gendocCommand = cli.Command{
 		Action: GenDoc,
 		Name:   "gendoc",
@@ -222,7 +237,12 @@ func init() {
 		advancedMode,
 	}
 	app.Action = signer
-	app.Commands = []cli.Command{initCommand, attestCommand, setCredentialCommand, delCredentialCommand, gendocCommand}
+	app.Commands = []cli.Command{initCommand,
+		attestCommand,
+		setCredentialCommand,
+		delCredentialCommand,
+		newAccountCommand,
+		gendocCommand}
 	cli.CommandHelpTemplate = utils.OriginCommandHelpTemplate
 }
 
@@ -382,6 +402,31 @@ func removeCredential(ctx *cli.Context) error {
 	return nil
 }
 
+func newAccount(c *cli.Context) error {
+	if err := initialize(c); err != nil {
+		return err
+	}
+	// The newaccount is meant for users using the CLI, since 'real' external
+	// UIs can use the UI-api instead. So we'll just use the native CLI UI here.
+	var (
+		ui                        = core.NewCommandlineUI()
+		pwStorage storage.Storage = &storage.NoStorage{}
+		ksLoc                     = c.GlobalString(keystoreFlag.Name)
+		lightKdf                  = c.GlobalBool(utils.LightKDFFlag.Name)
+	)
+	log.Info("Starting clef", "keystore", ksLoc, "light-kdf", lightKdf)
+	am := core.StartClefAccountManager(ksLoc, true, lightKdf, "")
+	// This gives is us access to the external API
+	apiImpl := core.NewSignerAPI(am, 0, true, ui, nil, false, pwStorage)
+	// This gives us access to the internal API
+	internalApi := core.NewUIServerAPI(apiImpl)
+	addr, err := internalApi.New(context.Background())
+	if err == nil {
+		fmt.Printf("Generated account %v\n", addr.String())
+	}
+	return err
+}
+
 func initialize(c *cli.Context) error {
 	// Set up the logger to print everything
 	logOutput := os.Stdout
@@ -457,7 +502,6 @@ func signer(c *cli.Context) error {
 		api       core.ExternalAPI
 		pwStorage storage.Storage = &storage.NoStorage{}
 	)
-
 	configDir := c.GlobalString(configdirFlag.Name)
 	if stretchedKey, err := readMasterKey(c, ui); err != nil {
 		log.Warn("Failed to open master, rules disabled", "err", err)
diff --git a/signer/core/api.go b/signer/core/api.go
index c10bf578d..7e6ece997 100644
--- a/signer/core/api.go
+++ b/signer/core/api.go
@@ -43,7 +43,7 @@ const (
 	// ExternalAPIVersion -- see extapi_changelog.md
 	ExternalAPIVersion = "6.0.0"
 	// InternalAPIVersion -- see intapi_changelog.md
-	InternalAPIVersion = "7.0.0"
+	InternalAPIVersion = "7.0.1"
 )
 
 // ExternalAPI defines the external API through which signing requests are made.
@@ -395,8 +395,7 @@ func (api *SignerAPI) List(ctx context.Context) ([]common.Address, error) {
 // the given password. Users are responsible to backup the private key that is stored
 // in the keystore location thas was specified when this API was created.
 func (api *SignerAPI) New(ctx context.Context) (common.Address, error) {
-	be := api.am.Backends(keystore.KeyStoreType)
-	if len(be) == 0 {
+	if be := api.am.Backends(keystore.KeyStoreType); len(be) == 0 {
 		return common.Address{}, errors.New("password based accounts not supported")
 	}
 	if resp, err := api.UI.ApproveNewAccount(&NewAccountRequest{MetadataFromContext(ctx)}); err != nil {
@@ -404,7 +403,16 @@ func (api *SignerAPI) New(ctx context.Context) (common.Address, error) {
 	} else if !resp.Approved {
 		return common.Address{}, ErrRequestDenied
 	}
+	return api.newAccount()
+}
 
+// newAccount is the internal method to create a new account. It should be used
+// _after_ user-approval has been obtained
+func (api *SignerAPI) newAccount() (common.Address, error) {
+	be := api.am.Backends(keystore.KeyStoreType)
+	if len(be) == 0 {
+		return common.Address{}, errors.New("password based accounts not supported")
+	}
 	// Three retries to get a valid password
 	for i := 0; i < 3; i++ {
 		resp, err := api.UI.OnInputRequired(UserInputRequest{
diff --git a/signer/core/uiapi.go b/signer/core/uiapi.go
index 36735d37e..25a587de5 100644
--- a/signer/core/uiapi.go
+++ b/signer/core/uiapi.go
@@ -195,6 +195,16 @@ func (api *UIServerAPI) Import(ctx context.Context, keyJSON json.RawMessage, old
 	return be[0].(*keystore.KeyStore).Import(keyJSON, oldPassphrase, newPassphrase)
 }
 
+// New creates a new password protected Account. The private key is protected with
+// the given password. Users are responsible to backup the private key that is stored
+// in the keystore location that was specified when this API was created.
+// This method is the same as New on the external API, the difference being that
+// this implementation does not ask for confirmation, since it's initiated by
+// the user
+func (api *UIServerAPI) New(ctx context.Context) (common.Address, error) {
+	return api.extApi.newAccount()
+}
+
 // Other methods to be added, not yet implemented are:
 // - Ruleset interaction: add rules, attest rulefiles
 // - Store metadata about accounts, e.g. naming of accounts
-- 
GitLab