diff options
Diffstat (limited to 'cmd')
-rw-r--r-- | cmd/ethkey/README.md | 41 | ||||
-rw-r--r-- | cmd/ethkey/generate.go | 117 | ||||
-rw-r--r-- | cmd/ethkey/inspect.go | 74 | ||||
-rw-r--r-- | cmd/ethkey/main.go | 70 | ||||
-rw-r--r-- | cmd/ethkey/message.go | 148 | ||||
-rw-r--r-- | cmd/ethkey/utils.go | 83 | ||||
-rw-r--r-- | cmd/evm/json_logger.go | 10 | ||||
-rw-r--r-- | cmd/faucet/faucet.go | 7 | ||||
-rw-r--r-- | cmd/utils/fdlimit_freebsd.go | 10 | ||||
-rw-r--r-- | cmd/utils/fdlimit_test.go | 12 | ||||
-rw-r--r-- | cmd/utils/fdlimit_unix.go | 10 | ||||
-rw-r--r-- | cmd/utils/fdlimit_windows.go | 6 |
12 files changed, 581 insertions, 7 deletions
diff --git a/cmd/ethkey/README.md b/cmd/ethkey/README.md new file mode 100644 index 000000000..cf72ba43d --- /dev/null +++ b/cmd/ethkey/README.md @@ -0,0 +1,41 @@ +ethkey +====== + +ethkey is a simple command-line tool for working with Ethereum keyfiles. + + +# Usage + +### `ethkey generate` + +Generate a new keyfile. +If you want to use an existing private key to use in the keyfile, it can be +specified by setting `--privatekey` with the location of the file containing the +private key. + + +### `ethkey inspect <keyfile>` + +Print various information about the keyfile. +Private key information can be printed by using the `--private` flag; +make sure to use this feature with great caution! + + +### `ethkey sign <keyfile> <message/file>` + +Sign the message with a keyfile. +It is possible to refer to a file containing the message. + + +### `ethkey verify <address> <signature> <message/file>` + +Verify the signature of the message. +It is possible to refer to a file containing the message. + + +## Passphrases + +For every command that uses a keyfile, you will be prompted to provide the +passphrase for decrypting the keyfile. To avoid this message, it is possible +to pass the passphrase by using the `--passphrase` flag pointing to a file that +contains the passphrase. diff --git a/cmd/ethkey/generate.go b/cmd/ethkey/generate.go new file mode 100644 index 000000000..dee0e9d70 --- /dev/null +++ b/cmd/ethkey/generate.go @@ -0,0 +1,117 @@ +package main + +import ( + "crypto/ecdsa" + "crypto/rand" + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/crypto" + "github.com/pborman/uuid" + "gopkg.in/urfave/cli.v1" +) + +type outputGenerate struct { + Address string + AddressEIP55 string +} + +var commandGenerate = cli.Command{ + Name: "generate", + Usage: "generate new keyfile", + ArgsUsage: "[ <keyfile> ]", + Description: ` +Generate a new keyfile. +If you want to use an existing private key to use in the keyfile, it can be +specified by setting --privatekey with the location of the file containing the +private key.`, + Flags: []cli.Flag{ + passphraseFlag, + jsonFlag, + cli.StringFlag{ + Name: "privatekey", + Usage: "the file from where to read the private key to " + + "generate a keyfile for", + }, + }, + Action: func(ctx *cli.Context) error { + // Check if keyfile path given and make sure it doesn't already exist. + keyfilepath := ctx.Args().First() + if keyfilepath == "" { + keyfilepath = defaultKeyfileName + } + if _, err := os.Stat(keyfilepath); err == nil { + utils.Fatalf("Keyfile already exists at %s.", keyfilepath) + } else if !os.IsNotExist(err) { + utils.Fatalf("Error checking if keyfile exists: %v", err) + } + + var privateKey *ecdsa.PrivateKey + + // First check if a private key file is provided. + privateKeyFile := ctx.String("privatekey") + if privateKeyFile != "" { + privateKeyBytes, err := ioutil.ReadFile(privateKeyFile) + if err != nil { + utils.Fatalf("Failed to read the private key file '%s': %v", + privateKeyFile, err) + } + + pk, err := crypto.HexToECDSA(string(privateKeyBytes)) + if err != nil { + utils.Fatalf( + "Could not construct ECDSA private key from file content: %v", + err) + } + privateKey = pk + } + + // If not loaded, generate random. + if privateKey == nil { + pk, err := ecdsa.GenerateKey(crypto.S256(), rand.Reader) + if err != nil { + utils.Fatalf("Failed to generate random private key: %v", err) + } + privateKey = pk + } + + // Create the keyfile object with a random UUID. + id := uuid.NewRandom() + key := &keystore.Key{ + Id: id, + Address: crypto.PubkeyToAddress(privateKey.PublicKey), + PrivateKey: privateKey, + } + + // Encrypt key with passphrase. + passphrase := getPassPhrase(ctx, true) + keyjson, err := keystore.EncryptKey(key, passphrase, + keystore.StandardScryptN, keystore.StandardScryptP) + if err != nil { + utils.Fatalf("Error encrypting key: %v", err) + } + + // Store the file to disk. + if err := os.MkdirAll(filepath.Dir(keyfilepath), 0700); err != nil { + utils.Fatalf("Could not create directory %s", filepath.Dir(keyfilepath)) + } + if err := ioutil.WriteFile(keyfilepath, keyjson, 0600); err != nil { + utils.Fatalf("Failed to write keyfile to %s: %v", keyfilepath, err) + } + + // Output some information. + out := outputGenerate{ + Address: key.Address.Hex(), + } + if ctx.Bool(jsonFlag.Name) { + mustPrintJSON(out) + } else { + fmt.Println("Address: ", out.Address) + } + return nil + }, +} diff --git a/cmd/ethkey/inspect.go b/cmd/ethkey/inspect.go new file mode 100644 index 000000000..8a7aeef84 --- /dev/null +++ b/cmd/ethkey/inspect.go @@ -0,0 +1,74 @@ +package main + +import ( + "encoding/hex" + "fmt" + "io/ioutil" + + "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/crypto" + "gopkg.in/urfave/cli.v1" +) + +type outputInspect struct { + Address string + PublicKey string + PrivateKey string +} + +var commandInspect = cli.Command{ + Name: "inspect", + Usage: "inspect a keyfile", + ArgsUsage: "<keyfile>", + Description: ` +Print various information about the keyfile. +Private key information can be printed by using the --private flag; +make sure to use this feature with great caution!`, + Flags: []cli.Flag{ + passphraseFlag, + jsonFlag, + cli.BoolFlag{ + Name: "private", + Usage: "include the private key in the output", + }, + }, + Action: func(ctx *cli.Context) error { + keyfilepath := ctx.Args().First() + + // Read key from file. + keyjson, err := ioutil.ReadFile(keyfilepath) + if err != nil { + utils.Fatalf("Failed to read the keyfile at '%s': %v", keyfilepath, err) + } + + // Decrypt key with passphrase. + passphrase := getPassPhrase(ctx, false) + key, err := keystore.DecryptKey(keyjson, passphrase) + if err != nil { + utils.Fatalf("Error decrypting key: %v", err) + } + + // Output all relevant information we can retrieve. + showPrivate := ctx.Bool("private") + out := outputInspect{ + Address: key.Address.Hex(), + PublicKey: hex.EncodeToString( + crypto.FromECDSAPub(&key.PrivateKey.PublicKey)), + } + if showPrivate { + out.PrivateKey = hex.EncodeToString(crypto.FromECDSA(key.PrivateKey)) + } + + if ctx.Bool(jsonFlag.Name) { + mustPrintJSON(out) + } else { + fmt.Println("Address: ", out.Address) + fmt.Println("Public key: ", out.PublicKey) + if showPrivate { + fmt.Println("Private key: ", out.PrivateKey) + } + } + return nil + }, +} diff --git a/cmd/ethkey/main.go b/cmd/ethkey/main.go new file mode 100644 index 000000000..b9b7a18e0 --- /dev/null +++ b/cmd/ethkey/main.go @@ -0,0 +1,70 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. + +package main + +import ( + "fmt" + "os" + + "github.com/ethereum/go-ethereum/cmd/utils" + "gopkg.in/urfave/cli.v1" +) + +const ( + defaultKeyfileName = "keyfile.json" +) + +var ( + gitCommit = "" // Git SHA1 commit hash of the release (set via linker flags) + + app *cli.App // the main app instance +) + +var ( // Commonly used command line flags. + passphraseFlag = cli.StringFlag{ + Name: "passwordfile", + Usage: "the file that contains the passphrase for the keyfile", + } + + jsonFlag = cli.BoolFlag{ + Name: "json", + Usage: "output JSON instead of human-readable format", + } + + messageFlag = cli.StringFlag{ + Name: "message", + Usage: "the file that contains the message to sign/verify", + } +) + +// Configure the app instance. +func init() { + app = utils.NewApp(gitCommit, "an Ethereum key manager") + app.Commands = []cli.Command{ + commandGenerate, + commandInspect, + commandSignMessage, + commandVerifyMessage, + } +} + +func main() { + if err := app.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/cmd/ethkey/message.go b/cmd/ethkey/message.go new file mode 100644 index 000000000..ae6b6552d --- /dev/null +++ b/cmd/ethkey/message.go @@ -0,0 +1,148 @@ +package main + +import ( + "encoding/hex" + "fmt" + "io/ioutil" + "os" + "strings" + + "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "gopkg.in/urfave/cli.v1" +) + +type outputSign struct { + Signature string +} + +var commandSignMessage = cli.Command{ + Name: "signmessage", + Usage: "sign a message", + ArgsUsage: "<keyfile> <message/file>", + Description: ` +Sign the message with a keyfile. +It is possible to refer to a file containing the message.`, + Flags: []cli.Flag{ + passphraseFlag, + jsonFlag, + }, + Action: func(ctx *cli.Context) error { + keyfilepath := ctx.Args().First() + message := []byte(ctx.Args().Get(1)) + + // Load the keyfile. + keyjson, err := ioutil.ReadFile(keyfilepath) + if err != nil { + utils.Fatalf("Failed to read the keyfile at '%s': %v", + keyfilepath, err) + } + + // Decrypt key with passphrase. + passphrase := getPassPhrase(ctx, false) + key, err := keystore.DecryptKey(keyjson, passphrase) + if err != nil { + utils.Fatalf("Error decrypting key: %v", err) + } + + if len(message) == 0 { + utils.Fatalf("A message must be provided") + } + // Read message if file. + if _, err := os.Stat(string(message)); err == nil { + message, err = ioutil.ReadFile(string(message)) + if err != nil { + utils.Fatalf("Failed to read the message file: %v", err) + } + } + + signature, err := crypto.Sign(signHash(message), key.PrivateKey) + if err != nil { + utils.Fatalf("Failed to sign message: %v", err) + } + + out := outputSign{ + Signature: hex.EncodeToString(signature), + } + if ctx.Bool(jsonFlag.Name) { + mustPrintJSON(out) + } else { + fmt.Println("Signature: ", out.Signature) + } + return nil + }, +} + +type outputVerify struct { + Success bool + RecoveredAddress string + RecoveredPublicKey string +} + +var commandVerifyMessage = cli.Command{ + Name: "verifymessage", + Usage: "verify the signature of a signed message", + ArgsUsage: "<address> <signature> <message/file>", + Description: ` +Verify the signature of the message. +It is possible to refer to a file containing the message.`, + Flags: []cli.Flag{ + jsonFlag, + }, + Action: func(ctx *cli.Context) error { + addressStr := ctx.Args().First() + signatureHex := ctx.Args().Get(1) + message := []byte(ctx.Args().Get(2)) + + // Determine whether it is a keyfile, public key or address. + if !common.IsHexAddress(addressStr) { + utils.Fatalf("Invalid address: %s", addressStr) + } + address := common.HexToAddress(addressStr) + + signature, err := hex.DecodeString(signatureHex) + if err != nil { + utils.Fatalf("Signature encoding is not hexadecimal: %v", err) + } + + if len(message) == 0 { + utils.Fatalf("A message must be provided") + } + // Read message if file. + if _, err := os.Stat(string(message)); err == nil { + message, err = ioutil.ReadFile(string(message)) + if err != nil { + utils.Fatalf("Failed to read the message file: %v", err) + } + } + + recoveredPubkey, err := crypto.SigToPub(signHash(message), signature) + if err != nil || recoveredPubkey == nil { + utils.Fatalf("Signature verification failed: %v", err) + } + recoveredPubkeyBytes := crypto.FromECDSAPub(recoveredPubkey) + recoveredAddress := crypto.PubkeyToAddress(*recoveredPubkey) + + success := address == recoveredAddress + + out := outputVerify{ + Success: success, + RecoveredPublicKey: hex.EncodeToString(recoveredPubkeyBytes), + RecoveredAddress: strings.ToLower(recoveredAddress.Hex()), + } + if ctx.Bool(jsonFlag.Name) { + mustPrintJSON(out) + } else { + if out.Success { + fmt.Println("Signature verification successful!") + } else { + fmt.Println("Signature verification failed!") + } + fmt.Println("Recovered public key: ", out.RecoveredPublicKey) + fmt.Println("Recovered address: ", out.RecoveredAddress) + } + return nil + }, +} diff --git a/cmd/ethkey/utils.go b/cmd/ethkey/utils.go new file mode 100644 index 000000000..0e563bf92 --- /dev/null +++ b/cmd/ethkey/utils.go @@ -0,0 +1,83 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. + +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "strings" + + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/console" + "github.com/ethereum/go-ethereum/crypto" + "gopkg.in/urfave/cli.v1" +) + +// getPassPhrase obtains a passphrase given by the user. It first checks the +// --passphrase command line flag and ultimately prompts the user for a +// passphrase. +func getPassPhrase(ctx *cli.Context, confirmation bool) string { + // Look for the --passphrase flag. + passphraseFile := ctx.String(passphraseFlag.Name) + if passphraseFile != "" { + content, err := ioutil.ReadFile(passphraseFile) + if err != nil { + utils.Fatalf("Failed to read passphrase file '%s': %v", + passphraseFile, err) + } + return strings.TrimRight(string(content), "\r\n") + } + + // Otherwise prompt the user for the passphrase. + passphrase, err := console.Stdin.PromptPassword("Passphrase: ") + if err != nil { + utils.Fatalf("Failed to read passphrase: %v", err) + } + if confirmation { + confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ") + if err != nil { + utils.Fatalf("Failed to read passphrase confirmation: %v", err) + } + if passphrase != confirm { + utils.Fatalf("Passphrases do not match") + } + } + return passphrase +} + +// signHash is a helper function that calculates a hash for the given message +// that can be safely used to calculate a signature from. +// +// The hash is calulcated as +// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}). +// +// This gives context to the signed message and prevents signing of transactions. +func signHash(data []byte) []byte { + msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data) + return crypto.Keccak256([]byte(msg)) +} + +// mustPrintJSON prints the JSON encoding of the given object and +// exits the program with an error message when the marshaling fails. +func mustPrintJSON(jsonObject interface{}) { + str, err := json.MarshalIndent(jsonObject, "", " ") + if err != nil { + utils.Fatalf("Failed to marshal JSON object: %v", err) + } + fmt.Println(string(str)) +} diff --git a/cmd/evm/json_logger.go b/cmd/evm/json_logger.go index eb7b0c466..47daf7dbb 100644 --- a/cmd/evm/json_logger.go +++ b/cmd/evm/json_logger.go @@ -19,6 +19,7 @@ package main import ( "encoding/json" "io" + "math/big" "time" "github.com/ethereum/go-ethereum/common" @@ -35,6 +36,10 @@ func NewJSONLogger(cfg *vm.LogConfig, writer io.Writer) *JSONLogger { return &JSONLogger{json.NewEncoder(writer), cfg} } +func (l *JSONLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error { + return nil +} + // CaptureState outputs state information on the logger. func (l *JSONLogger) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error { log := vm.StructLog{ @@ -56,6 +61,11 @@ func (l *JSONLogger) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cos return l.encoder.Encode(log) } +// CaptureFault outputs state information on the logger. +func (l *JSONLogger) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error { + return nil +} + // CaptureEnd is triggered at end of execution. func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error { type endLog struct { diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 5f16f2978..75ec124c1 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -21,7 +21,6 @@ package main import ( "bytes" - "compress/zlib" "context" "encoding/json" "errors" @@ -698,11 +697,7 @@ func authTwitter(url string) (string, string, common.Address, error) { } defer res.Body.Close() - reader, err := zlib.NewReader(res.Body) - if err != nil { - return "", "", common.Address{}, err - } - body, err := ioutil.ReadAll(reader) + body, err := ioutil.ReadAll(res.Body) if err != nil { return "", "", common.Address{}, err } diff --git a/cmd/utils/fdlimit_freebsd.go b/cmd/utils/fdlimit_freebsd.go index 4cb5013c8..f9ed8937e 100644 --- a/cmd/utils/fdlimit_freebsd.go +++ b/cmd/utils/fdlimit_freebsd.go @@ -52,3 +52,13 @@ func getFdLimit() (int, error) { } return int(limit.Cur), nil } + +// getFdMaxLimit retrieves the maximum number of file descriptors this process is +// allowed to request for itself. +func getFdMaxLimit() (int, error) { + var limit syscall.Rlimit + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil { + return 0, err + } + return int(limit.Max), nil +} diff --git a/cmd/utils/fdlimit_test.go b/cmd/utils/fdlimit_test.go index 0a950a6c9..48489cf4c 100644 --- a/cmd/utils/fdlimit_test.go +++ b/cmd/utils/fdlimit_test.go @@ -16,12 +16,22 @@ package utils -import "testing" +import ( + "fmt" + "testing" +) // TestFileDescriptorLimits simply tests whether the file descriptor allowance // per this process can be retrieved. func TestFileDescriptorLimits(t *testing.T) { target := 4096 + hardlimit, err := getFdMaxLimit() + if err != nil { + t.Fatal(err) + } + if hardlimit < target { + t.Skip(fmt.Sprintf("system limit is less than desired test target: %d < %d", hardlimit, target)) + } if limit, err := getFdLimit(); err != nil || limit <= 0 { t.Fatalf("failed to retrieve file descriptor limit (%d): %v", limit, err) diff --git a/cmd/utils/fdlimit_unix.go b/cmd/utils/fdlimit_unix.go index 08e153bbd..c08d1fab0 100644 --- a/cmd/utils/fdlimit_unix.go +++ b/cmd/utils/fdlimit_unix.go @@ -48,3 +48,13 @@ func getFdLimit() (int, error) { } return int(limit.Cur), nil } + +// getFdMaxLimit retrieves the maximum number of file descriptors this process is +// allowed to request for itself. +func getFdMaxLimit() (int, error) { + var limit syscall.Rlimit + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil { + return 0, err + } + return int(limit.Max), nil +} diff --git a/cmd/utils/fdlimit_windows.go b/cmd/utils/fdlimit_windows.go index 53aad3d7a..f239683d2 100644 --- a/cmd/utils/fdlimit_windows.go +++ b/cmd/utils/fdlimit_windows.go @@ -39,3 +39,9 @@ func getFdLimit() (int, error) { // Please see raiseFdLimit for the reason why we use hard coded 16K as the limit return 16384, nil } + +// getFdMaxLimit retrieves the maximum number of file descriptors this process is +// allowed to request for itself. +func getFdMaxLimit() (int, error) { + return getFdLimit() +} |