diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1dc9eab..cee296d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -70,7 +70,7 @@ jobs: mv ethdo-linux-arm64 ethdo tar zcf ethdo-${RELEASE_VERSION}-linux-arm64.tar.gz ethdo - - name: Create draft release + - name: Create release id: create_release uses: actions/create-release@v1 env: @@ -78,7 +78,7 @@ jobs: with: tag_name: ${{ github.ref }} release_name: Release ${{ env.RELEASE_VERSION }} - draft: true + draft: false prerelease: false - name: Upload windows zip file diff --git a/cmd/accountcreate.go b/cmd/accountcreate.go index f12f7a2..a026783 100644 --- a/cmd/accountcreate.go +++ b/cmd/accountcreate.go @@ -33,9 +33,12 @@ var accountCreateCmd = &cobra.Command{ In quiet mode this will return 0 if the account is created successfully, otherwise 1.`, Run: func(cmd *cobra.Command, args []string) { + ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout")) + defer cancel() + assert(viper.GetString("account") != "", "--account is required") - wallet, err := openWallet() + wallet, err := walletFromInput(ctx) errCheck(err, "Failed to access wallet") outputIf(debug, fmt.Sprintf("Opened wallet %q of type %s", wallet.Name(), wallet.Type())) if wallet.Type() == "hierarchical deterministic" { @@ -43,8 +46,6 @@ In quiet mode this will return 0 if the account is created successfully, otherwi } locker, isLocker := wallet.(e2wtypes.WalletLocker) if isLocker { - ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout")) - defer cancel() errCheck(locker.Unlock(ctx, []byte(getWalletPassphrase())), "Failed to unlock wallet") } diff --git a/cmd/accountimport.go b/cmd/accountimport.go index 2108c15..ca27163 100644 --- a/cmd/accountimport.go +++ b/cmd/accountimport.go @@ -47,13 +47,13 @@ In quiet mode this will return 0 if the account is imported successfully, otherw key, err := bytesutil.FromHexString(accountImportKey) errCheck(err, "Invalid key") - w, err := walletFromPath(viper.GetString("account")) + w, err := walletFromPath(ctx, viper.GetString("account")) errCheck(err, "Failed to access wallet") _, ok := w.(e2wtypes.WalletAccountImporter) assert(ok, fmt.Sprintf("wallets of type %q do not allow importing accounts", w.Type())) - _, err = accountFromPath(ctx, viper.GetString("account")) + _, _, err = walletAndAccountFromPath(ctx, viper.GetString("account")) assert(err != nil, "Account already exists") locker, isLocker := w.(e2wtypes.WalletLocker) diff --git a/cmd/accountinfo.go b/cmd/accountinfo.go index b9b4c45..c5b88fb 100644 --- a/cmd/accountinfo.go +++ b/cmd/accountinfo.go @@ -17,13 +17,11 @@ import ( "context" "fmt" "os" - "strings" "github.com/spf13/cobra" "github.com/spf13/viper" e2types "github.com/wealdtech/go-eth2-types/v2" util "github.com/wealdtech/go-eth2-util" - e2wallet "github.com/wealdtech/go-eth2-wallet" e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2" ) @@ -36,30 +34,11 @@ var accountInfoCmd = &cobra.Command{ In quiet mode this will return 0 if the account exists, otherwise 1.`, Run: func(cmd *cobra.Command, args []string) { - assert(viper.GetString("account") != "", "--account is required") - - wallet, err := openWallet() - errCheck(err, "Failed to access wallet") - outputIf(debug, fmt.Sprintf("Opened wallet %q of type %s", wallet.Name(), wallet.Type())) - - _, accountName, err := e2wallet.WalletAndAccountNames(viper.GetString("account")) - errCheck(err, "Failed to obtain account name") - - if wallet.Type() == "hierarchical deterministic" && strings.HasPrefix(accountName, "m/") { - assert(getWalletPassphrase() != "", "walletpassphrase is required to show information about dynamically generated hierarchical deterministic accounts") - locker, isLocker := wallet.(e2wtypes.WalletLocker) - if isLocker { - ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout")) - defer cancel() - errCheck(locker.Unlock(ctx, []byte(getWalletPassphrase())), "Failed to unlock wallet") - } - } - - accountByNameProvider, isAccountByNameProvider := wallet.(e2wtypes.WalletAccountByNameProvider) - assert(isAccountByNameProvider, "wallet cannot obtain accounts by name") ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout")) defer cancel() - account, err := accountByNameProvider.AccountByName(ctx, accountName) + + assert(viper.GetString("account") != "", "--account is required") + wallet, account, err := walletAndAccountFromInput(ctx) errCheck(err, "Failed to obtain account") // Disallow wildcards (for now) diff --git a/cmd/accountkey.go b/cmd/accountkey.go index 2c379c1..f5095df 100644 --- a/cmd/accountkey.go +++ b/cmd/accountkey.go @@ -17,11 +17,9 @@ import ( "context" "fmt" "os" - "strings" "github.com/spf13/cobra" "github.com/spf13/viper" - e2wallet "github.com/wealdtech/go-eth2-wallet" e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2" ) @@ -35,31 +33,13 @@ var accountKeyCmd = &cobra.Command{ In quiet mode this will return 0 if the key can be obtained, otherwise 1.`, Run: func(cmd *cobra.Command, args []string) { + ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout")) + defer cancel() + assert(!remote, "account keys not available with remote wallets") assert(viper.GetString("account") != "", "--account is required") - wallet, err := openWallet() - errCheck(err, "Failed to access wallet") - outputIf(debug, fmt.Sprintf("Opened wallet %q of type %s", wallet.Name(), wallet.Type())) - - _, accountName, err := e2wallet.WalletAndAccountNames(viper.GetString("account")) - errCheck(err, "Failed to obtain account name") - - if wallet.Type() == "hierarchical deterministic" && strings.HasPrefix(accountName, "m/") { - assert(getWalletPassphrase() != "", "walletpassphrase is required to show information about dynamically generated hierarchical deterministic accounts") - locker, isLocker := wallet.(e2wtypes.WalletLocker) - if isLocker { - ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout")) - defer cancel() - errCheck(locker.Unlock(ctx, []byte(getWalletPassphrase())), "Failed to unlock wallet") - } - } - - accountByNameProvider, isAccountByNameProvider := wallet.(e2wtypes.WalletAccountByNameProvider) - assert(isAccountByNameProvider, "wallet cannot obtain accounts by name") - ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout")) - defer cancel() - account, err := accountByNameProvider.AccountByName(ctx, accountName) + _, account, err := walletAndAccountFromInput(ctx) errCheck(err, "Failed to obtain account") privateKeyProvider, isPrivateKeyProvider := account.(e2wtypes.AccountPrivateKeyProvider) diff --git a/cmd/accountlock.go b/cmd/accountlock.go index 8daebcb..d76af31 100644 --- a/cmd/accountlock.go +++ b/cmd/accountlock.go @@ -18,7 +18,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - e2wallet "github.com/wealdtech/go-eth2-wallet" e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2" ) @@ -31,27 +30,18 @@ var accountLockCmd = &cobra.Command{ In quiet mode this will return 0 if the account is locked, otherwise 1.`, Run: func(cmd *cobra.Command, args []string) { - assert(viper.GetString("account") != "", "--account is required") - - wallet, err := openWallet() - errCheck(err, "Failed to access wallet") - - _, accountName, err := e2wallet.WalletAndAccountNames(viper.GetString("account")) - errCheck(err, "Failed to obtain account name") - - accountByNameProvider, isAccountByNameProvider := wallet.(e2wtypes.WalletAccountByNameProvider) - assert(isAccountByNameProvider, "wallet cannot obtain accounts by name") ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout")) defer cancel() - account, err := accountByNameProvider.AccountByName(ctx, accountName) + + assert(viper.GetString("account") != "", "--account is required") + + _, account, err := walletAndAccountFromInput(ctx) errCheck(err, "Failed to obtain account") locker, isLocker := account.(e2wtypes.AccountLocker) assert(isLocker, "Account does not support locking") - ctx, cancel = context.WithTimeout(context.Background(), viper.GetDuration("timeout")) err = locker.Lock(ctx) - cancel() errCheck(err, "Failed to lock account") }, } diff --git a/cmd/accountunlock.go b/cmd/accountunlock.go index 232518f..8ac4f5a 100644 --- a/cmd/accountunlock.go +++ b/cmd/accountunlock.go @@ -19,7 +19,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - e2wallet "github.com/wealdtech/go-eth2-wallet" e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2" ) @@ -32,19 +31,12 @@ var accountUnlockCmd = &cobra.Command{ In quiet mode this will return 0 if the account is unlocked, otherwise 1.`, Run: func(cmd *cobra.Command, args []string) { - assert(viper.GetString("account") != "", "--account is required") - - wallet, err := openWallet() - errCheck(err, "Failed to access wallet") - - _, accountName, err := e2wallet.WalletAndAccountNames(viper.GetString("account")) - errCheck(err, "Failed to obtain account name") - - accountByNameProvider, isAccountByNameProvider := wallet.(e2wtypes.WalletAccountByNameProvider) - assert(isAccountByNameProvider, "wallet cannot obtain accounts by name") ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout")) defer cancel() - account, err := accountByNameProvider.AccountByName(ctx, accountName) + + assert(viper.GetString("account") != "", "--account is required") + + _, account, err := walletAndAccountFromInput(ctx) errCheck(err, "Failed to obtain account") locker, isLocker := account.(e2wtypes.AccountLocker) diff --git a/cmd/accountwithdrawalcredentials.go b/cmd/accountwithdrawalcredentials.go index 9d408bd..6884c66 100644 --- a/cmd/accountwithdrawalcredentials.go +++ b/cmd/accountwithdrawalcredentials.go @@ -23,8 +23,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" util "github.com/wealdtech/go-eth2-util" - e2wallet "github.com/wealdtech/go-eth2-wallet" - e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2" ) var accountWithdrawalCredentialsCmd = &cobra.Command{ @@ -47,15 +45,7 @@ In quiet mode this will return 0 if the account exists, otherwise 1.`, pubKey, err = hex.DecodeString(strings.TrimPrefix(viper.GetString("pubkey"), "0x")) errCheck(err, "Failed to decode supplied public key") } else { - wallet, err := openWallet() - errCheck(err, "Failed to access wallet") - - _, accountName, err := e2wallet.WalletAndAccountNames(viper.GetString("account")) - errCheck(err, "Failed to obtain account name") - - accountByNameProvider, isAccountByNameProvider := wallet.(e2wtypes.WalletAccountByNameProvider) - assert(isAccountByNameProvider, "wallet cannot obtain accounts by name") - account, err := accountByNameProvider.AccountByName(ctx, accountName) + _, account, err := walletAndAccountFromInput(ctx) errCheck(err, "Failed to obtain account") key, err := bestPublicKey(account) diff --git a/cmd/depositverify.go b/cmd/depositverify.go index 21bf5b9..694c16a 100644 --- a/cmd/depositverify.go +++ b/cmd/depositverify.go @@ -60,18 +60,17 @@ In quiet mode this will return 0 if the the data can be generated correctly, oth deposits, err := depositDataFromJSON(depositVerifyData) errCheck(err, "Failed to fetch deposit data") - withdrawalCredentials := "" + var withdrawalCredentials []byte if depositVerifyWithdrawalPubKey != "" { withdrawalPubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(depositVerifyWithdrawalPubKey, "0x")) errCheck(err, "Invalid withdrawal public key") assert(len(withdrawalPubKeyBytes) == 48, "Public key should be 48 bytes") withdrawalPubKey, err := e2types.BLSPublicKeyFromBytes(withdrawalPubKeyBytes) errCheck(err, "Value supplied with --withdrawalpubkey is not a valid public key") - withdrawalBytes := util.SHA256(withdrawalPubKey.Marshal()) - withdrawalBytes[0] = 0 // BLS_WITHDRAWAL_PREFIX - withdrawalCredentials = fmt.Sprintf("%x", withdrawalBytes) + withdrawalCredentials = util.SHA256(withdrawalPubKey.Marshal()) + withdrawalCredentials[0] = 0 // BLS_WITHDRAWAL_PREFIX } - outputIf(debug, fmt.Sprintf("Withdrawal credentials are %s", withdrawalCredentials)) + outputIf(debug, fmt.Sprintf("Withdrawal credentials are %#x", withdrawalCredentials)) depositValue := uint64(0) if depositVerifyDepositValue != "" { @@ -81,7 +80,7 @@ In quiet mode this will return 0 if the the data can be generated correctly, oth assert(depositValue >= 1000000000, "deposit value must be at least 1 Ether") // MIN_DEPOSIT_AMOUNT } - validatorPubKeys := make(map[string]bool) + validatorPubKeys := make(map[[48]byte]bool) if depositVerifyValidatorPubKey != "" { validatorPubKeys, err = validatorPubKeysFromInput(depositVerifyValidatorPubKey) errCheck(err, "Failed to obtain validator public key(s))") @@ -89,8 +88,10 @@ In quiet mode this will return 0 if the the data can be generated correctly, oth failures := false for i, deposit := range deposits { - if withdrawalCredentials != "" { - if deposit.WithdrawalCredentials != withdrawalCredentials { + if withdrawalCredentials != nil { + depositWithdrawalCredentials, err := hex.DecodeString(strings.TrimPrefix(deposit.WithdrawalCredentials, "0x")) + errCheck(err, fmt.Sprintf("Invalid withdrawal public key for deposit %d", i)) + if !bytes.Equal(depositWithdrawalCredentials, withdrawalCredentials) { outputIf(!quiet, fmt.Sprintf("Invalid withdrawal credentials for deposit %d", i)) failures = true } @@ -102,7 +103,11 @@ In quiet mode this will return 0 if the the data can be generated correctly, oth } } if len(validatorPubKeys) != 0 { - if _, exists := validatorPubKeys[deposit.PublicKey]; !exists { + depositValidatorPubKey, err := hex.DecodeString(strings.TrimPrefix(deposit.PublicKey, "0x")) + errCheck(err, fmt.Sprintf("Invalid validator public key for deposit %d", i)) + var key [48]byte + copy(key[:], depositValidatorPubKey) + if _, exists := validatorPubKeys[key]; !exists { outputIf(!quiet, fmt.Sprintf("Unknown validator public key for deposit %d", i)) failures = true } @@ -117,8 +122,8 @@ In quiet mode this will return 0 if the the data can be generated correctly, oth }, } -func validatorPubKeysFromInput(input string) (map[string]bool, error) { - pubKeys := make(map[string]bool) +func validatorPubKeysFromInput(input string) (map[[48]byte]bool, error) { + pubKeys := make(map[[48]byte]bool) var err error var data []byte // Input could be a public key or a path to public keys. @@ -135,7 +140,9 @@ func validatorPubKeysFromInput(input string) (map[string]bool, error) { if err != nil { return nil, errors.Wrap(err, "invalid public key") } - pubKeys[fmt.Sprintf("%x", pubKey.Marshal())] = true + var key [48]byte + copy(key[:], pubKey.Marshal()) + pubKeys[key] = true } else { // Assume it's a path to a file of public keys. data, err = ioutil.ReadFile(input) @@ -161,7 +168,9 @@ func validatorPubKeysFromInput(input string) (map[string]bool, error) { if err != nil { return nil, errors.Wrap(err, "invalid public key") } - pubKeys[fmt.Sprintf("%x", pubKey.Marshal())] = true + var key [48]byte + copy(key[:], pubKey.Marshal()) + pubKeys[key] = true } } diff --git a/cmd/exit.go b/cmd/exit.go new file mode 100644 index 0000000..02d5bb7 --- /dev/null +++ b/cmd/exit.go @@ -0,0 +1,32 @@ +// Copyright © 2019 Weald Technology Trading +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "github.com/spf13/cobra" +) + +// exitCmd represents the exit command +var exitCmd = &cobra.Command{ + Use: "exit", + Short: "Manage Ethereum 2 voluntary exits", + Long: `Manage Ethereum 2 voluntary exits.`, +} + +func init() { + RootCmd.AddCommand(exitCmd) +} + +func exitFlags(cmd *cobra.Command) { +} diff --git a/cmd/exitverify.go b/cmd/exitverify.go new file mode 100644 index 0000000..7b08c4c --- /dev/null +++ b/cmd/exitverify.go @@ -0,0 +1,137 @@ +// Copyright © 2020 Weald Technology Trading +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + "encoding/hex" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "strings" + + "github.com/pkg/errors" + ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/wealdtech/ethdo/grpc" + "github.com/wealdtech/ethdo/util" + e2types "github.com/wealdtech/go-eth2-types/v2" + e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2" +) + +var exitVerifyPubKey string + +var exitVerifyCmd = &cobra.Command{ + Use: "verify", + Short: "Verify deposit data matches requirements", + Long: `Verify deposit data matches requirements. For example: + + ethdo deposit verify --data=depositdata.json --withdrawalaccount=primary/current --value="32 Ether" + +The information generated can be passed to ethereal to create a deposit from the Ethereum 1 chain. + +In quiet mode this will return 0 if the the data can be generated correctly, otherwise 1.`, + Run: func(cmd *cobra.Command, args []string) { + ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout")) + defer cancel() + + assert(viper.GetString("account") != "" || exitVerifyPubKey != "", "account or public key is required") + account, err := exitVerifyAccount(ctx) + errCheck(err, "Failed to obtain account") + + assert(viper.GetString("exit.data") != "", "exit data is required") + data, err := obtainExitData(viper.GetString("exit.Data")) + errCheck(err, "Failed to obtain exit data") + + // Confirm signature is good. + err = connect() + errCheck(err, "Failed to obtain connection to Ethereum 2 beacon chain node") + genesisValidatorsRoot, err := grpc.FetchGenesisValidatorsRoot(eth2GRPCConn) + outputIf(debug, fmt.Sprintf("Genesis validators root is %x", genesisValidatorsRoot)) + errCheck(err, "Failed to obtain genesis validators root") + domain := e2types.Domain(e2types.DomainVoluntaryExit, data.ForkVersion, genesisValidatorsRoot) + exit := ðpb.VoluntaryExit{ + Epoch: data.Epoch, + ValidatorIndex: data.ValidatorIndex, + } + sig, err := e2types.BLSSignatureFromBytes(data.Signature) + errCheck(err, "Invalid signature") + verified, err := verifyStruct(account, exit, domain, sig) + errCheck(err, "Failed to verify voluntary exit") + assert(verified, "Voluntary exit failed to verify") + + // TODO confirm fork version is valid (once we have a way of obtaining the current fork version). + + outputIf(verbose, "Verified") + os.Exit(_exitSuccess) + }, +} + +// obtainExitData obtains exit data from an input, could be JSON itself or a path to JSON. +func obtainExitData(input string) (*validatorExitData, error) { + var err error + var data []byte + // Input could be JSON or a path to JSON + if strings.HasPrefix(input, "{") { + // Looks like JSON + data = []byte(input) + } else { + // Assume it's a path to JSON + data, err = ioutil.ReadFile(input) + if err != nil { + return nil, errors.Wrap(err, "failed to find deposit data file") + } + } + exitData := &validatorExitData{} + err = json.Unmarshal([]byte(data), exitData) + if err != nil { + return nil, errors.Wrap(err, "data is not valid JSON") + } + + return exitData, nil +} + +// exitVerifyAccount obtains the account for the exitVerify command. +func exitVerifyAccount(ctx context.Context) (e2wtypes.Account, error) { + var account e2wtypes.Account + var err error + if viper.GetString("account") != "" { + _, account, err = walletAndAccountFromPath(ctx, viper.GetString("account")) + if err != nil { + return nil, errors.Wrap(err, "failed to obtain account") + } + } else { + pubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(exitVerifyPubKey, "0x")) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("failed to decode public key %s", exitVerifyPubKey)) + } + account, err = util.NewScratchAccount(nil, pubKeyBytes) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("invalid public key %s", exitVerifyPubKey)) + } + } + return account, nil +} + +func init() { + exitCmd.AddCommand(exitVerifyCmd) + exitFlags(exitVerifyCmd) + exitVerifyCmd.Flags().String("data", "", "JSON data, or path to JSON data") + exitVerifyCmd.Flags().StringVar(&exitVerifyPubKey, "pubkey", "", "Public key for which to verify exit") + if err := viper.BindPFlag("exit.data", exitVerifyCmd.Flags().Lookup("data")); err != nil { + panic(err) + } +} diff --git a/cmd/root.go b/cmd/root.go index 6e9bd40..2e24c7d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -233,12 +233,32 @@ func outputIf(condition bool, msg string) { } } +// walletFromInput obtains a wallet given the information in the viper variable "wallet". +func walletFromInput(ctx context.Context) (e2wtypes.Wallet, error) { + return walletFromPath(ctx, viper.GetString("wallet")) +} + // walletFromPath obtains a wallet given a path specification. -func walletFromPath(path string) (e2wtypes.Wallet, error) { +func walletFromPath(ctx context.Context, path string) (e2wtypes.Wallet, error) { walletName, _, err := e2wallet.WalletAndAccountNames(path) if err != nil { return nil, err } + if viper.GetString("remote") != "" { + assert(viper.GetString("client-cert") != "", "remote connections require client-cert") + assert(viper.GetString("client-key") != "", "remote connections require client-key") + credentials, err := dirk.ComposeCredentials(ctx, viper.GetString("client-cert"), viper.GetString("client-key"), viper.GetString("server-ca-cert")) + if err != nil { + return nil, errors.Wrap(err, "failed to build dirk credentials") + } + + endpoints, err := remotesToEndpoints([]string{viper.GetString("remote")}) + if err != nil { + return nil, errors.Wrap(err, "failed to parse remote servers") + } + + return dirk.OpenWallet(ctx, walletName, credentials, endpoints) + } wallet, err := e2wallet.OpenWallet(walletName) if err != nil { if strings.Contains(err.Error(), "failed to decrypt wallet") { @@ -249,18 +269,23 @@ func walletFromPath(path string) (e2wtypes.Wallet, error) { return wallet, nil } -// accountFromPath obtains an account given a path specification. -func accountFromPath(ctx context.Context, path string) (e2wtypes.Account, error) { - wallet, err := walletFromPath(path) +// walletAndAccountFromInput obtains the wallet and account given the information in the viper variable "account". +func walletAndAccountFromInput(ctx context.Context) (e2wtypes.Wallet, e2wtypes.Account, error) { + return walletAndAccountFromPath(ctx, viper.GetString("account")) +} + +// walletAndAccountFromPath obtains the wallet and account given a path specification. +func walletAndAccountFromPath(ctx context.Context, path string) (e2wtypes.Wallet, e2wtypes.Account, error) { + wallet, err := walletFromPath(ctx, path) if err != nil { - return nil, err + return nil, nil, errors.Wrap(err, "faild to open wallet for account") } _, accountName, err := e2wallet.WalletAndAccountNames(path) if err != nil { - return nil, err + return nil, nil, errors.Wrap(err, "failed to obtain accout name") } if accountName == "" { - return nil, errors.New("no account name") + return nil, nil, errors.New("no account name") } if wallet.Type() == "hierarchical deterministic" && strings.HasPrefix(accountName, "m/") { @@ -270,7 +295,7 @@ func accountFromPath(ctx context.Context, path string) (e2wtypes.Account, error) if isLocker { err = locker.Unlock(ctx, []byte(viper.GetString("wallet-passphrase"))) if err != nil { - return nil, errors.New("invalid wallet passphrase") + return nil, nil, errors.New("failed to unlock wallet") } defer relockAccount(locker) } @@ -278,21 +303,30 @@ func accountFromPath(ctx context.Context, path string) (e2wtypes.Account, error) accountByNameProvider, isAccountByNameProvider := wallet.(e2wtypes.WalletAccountByNameProvider) if !isAccountByNameProvider { - return nil, errors.New("wallet cannot obtain accounts by name") + return nil, nil, errors.New("wallet cannot obtain accounts by name") } - return accountByNameProvider.AccountByName(ctx, accountName) + account, err := accountByNameProvider.AccountByName(ctx, accountName) + if err != nil { + return nil, nil, errors.Wrap(err, "failed to obtain account") + } + return wallet, account, nil } -// accountsFromPath obtains 0 or more accounts given a path specification. -func accountsFromPath(ctx context.Context, wallet e2wtypes.Wallet, accountSpec string) ([]e2wtypes.Account, error) { +// walletAndAccountsFromPath obtains the wallet and matching accounts given a path specification. +func walletAndAccountsFromPath(ctx context.Context, path string) (e2wtypes.Wallet, []e2wtypes.Account, error) { + wallet, err := walletFromPath(ctx, path) + if err != nil { + return nil, nil, errors.Wrap(err, "faild to open wallet for account") + } + accounts := make([]e2wtypes.Account, 0) - if accountSpec == "" { - accountSpec = "^.*$" + if path == "" { + path = "^.*$" } else { - accountSpec = fmt.Sprintf("^%s$", accountSpec) + path = fmt.Sprintf("^%s$", path) } - re := regexp.MustCompile(accountSpec) + re := regexp.MustCompile(path) for account := range wallet.Accounts(ctx) { if re.Match([]byte(account.Name())) { @@ -305,7 +339,7 @@ func accountsFromPath(ctx context.Context, wallet e2wtypes.Wallet, accountSpec s return accounts[i].Name() < accounts[j].Name() }) - return accounts, nil + return wallet, accounts, nil } // connect connects to an Ethereum 2 endpoint. @@ -369,46 +403,6 @@ func remotesToEndpoints(remotes []string) ([]*dirk.Endpoint, error) { return endpoints, nil } -// Oepn a wallet, local or remote. -func openWallet() (e2wtypes.Wallet, error) { - var err error - // Obtain the name of the wallet. - walletName := viper.GetString("wallet") - if walletName == "" { - walletName, _, err = e2wallet.WalletAndAccountNames(viper.GetString("account")) - } - if err != nil { - return nil, errors.Wrap(err, "failed to obtain wallet name") - } - if walletName == "" { - return nil, errors.New("no wallet name provided") - } - - return openNamedWallet(walletName) -} - -// Open a named wallet, local or remote. -func openNamedWallet(walletName string) (e2wtypes.Wallet, error) { - if viper.GetString("remote") != "" { - ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout")) - defer cancel() - assert(viper.GetString("client-cert") != "", "remote connections require client-cert") - assert(viper.GetString("client-key") != "", "remote connections require client-key") - credentials, err := dirk.ComposeCredentials(ctx, viper.GetString("client-cert"), viper.GetString("client-key"), viper.GetString("server-ca-cert")) - if err != nil { - return nil, errors.Wrap(err, "failed to build dirk credentials") - } - - endpoints, err := remotesToEndpoints([]string{viper.GetString("remote")}) - if err != nil { - return nil, errors.Wrap(err, "failed to parse remote servers") - } - - return dirk.OpenWallet(ctx, walletName, credentials, endpoints) - } - return walletFromPath(walletName) -} - // relockAccount locks an account; generally called as a defer after an account is unlocked. func relockAccount(locker e2wtypes.AccountLocker) { errCheck(locker.Lock(context.Background()), "failed to re-lock account") diff --git a/cmd/signaturesign.go b/cmd/signaturesign.go index 9646949..cd69e51 100644 --- a/cmd/signaturesign.go +++ b/cmd/signaturesign.go @@ -22,8 +22,6 @@ import ( "github.com/spf13/viper" "github.com/wealdtech/go-bytesutil" e2types "github.com/wealdtech/go-eth2-types/v2" - e2wallet "github.com/wealdtech/go-eth2-wallet" - e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2" ) // signatureSignCmd represents the signature sign command @@ -53,17 +51,7 @@ In quiet mode this will return 0 if the data can be signed, otherwise 1.`, outputIf(debug, fmt.Sprintf("Domain is %#x", domain)) assert(viper.GetString("account") != "", "--account is required") - - wallet, err := openWallet() - errCheck(err, "Failed to access wallet") - - _, accountName, err := e2wallet.WalletAndAccountNames(viper.GetString("account")) - errCheck(err, "Failed to obtain account name") - - accountByNameProvider, isAccountByNameProvider := wallet.(e2wtypes.WalletAccountByNameProvider) - assert(isAccountByNameProvider, "wallet does not support obtaining accounts by name") - - account, err := accountByNameProvider.AccountByName(ctx, accountName) + _, account, err := walletAndAccountFromInput(ctx) errCheck(err, "Failed to obtain account") var fixedSizeData [32]byte diff --git a/cmd/signatureverify.go b/cmd/signatureverify.go index 512c3db..2da49ea 100644 --- a/cmd/signatureverify.go +++ b/cmd/signatureverify.go @@ -15,15 +15,17 @@ package cmd import ( "context" + "encoding/hex" "fmt" "os" + "strings" - "github.com/prysmaticlabs/go-ssz" + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/wealdtech/ethdo/util" "github.com/wealdtech/go-bytesutil" e2types "github.com/wealdtech/go-eth2-types/v2" - e2wallet "github.com/wealdtech/go-eth2-wallet" e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2" ) @@ -58,49 +60,44 @@ In quiet mode this will return 0 if the data can be signed, otherwise 1.`, assert(len(domain) == 32, "Domain data invalid") } - var pubKey e2types.PublicKey - assert(signatureVerifySigner != "" || viper.GetString("account") != "", "Either --signer or --account should be supplied") - if viper.GetString("account") != "" { - wallet, err := openWallet() - errCheck(err, "Failed to access wallet") - _, accountName, err := e2wallet.WalletAndAccountNames(viper.GetString("account")) - errCheck(err, "Failed to obtain account name") + account, err := signatureVerifyAccount() + errCheck(err, "Failed to obtain account") + outputIf(debug, fmt.Sprintf("Public key is %#x", account.PublicKey().Marshal())) - accountByNameProvider, isAccountByNameProvider := wallet.(e2wtypes.WalletAccountByNameProvider) - assert(isAccountByNameProvider, "wallet cannot obtain accounts by name") - ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout")) - defer cancel() - account, err := accountByNameProvider.AccountByName(ctx, accountName) - errCheck(err, "Failed to obtain account") - pubKey, err = bestPublicKey(account) - errCheck(err, "Failed to obtain account's public key") - } else { - pubKeyBytes, err := bytesutil.FromHexString(signatureVerifySigner) - errCheck(err, "Invalid public key") - pubKey, err = e2types.BLSPublicKeyFromBytes(pubKeyBytes) - errCheck(err, "Invalid public key") - } - outputIf(debug, fmt.Sprintf("Public key is %#x", pubKey.Marshal())) - container := &signingContainer{ - Root: data, - Domain: domain, - } - outputIf(debug, fmt.Sprintf("Data root is %#x", data)) - outputIf(debug, fmt.Sprintf("Domain is %#x", domain)) - root, err := ssz.HashTreeRoot(container) - errCheck(err, "Failed to create signing root") - outputIf(debug, fmt.Sprintf("Signing root is %#x", root)) + var root [32]byte + copy(root[:], data) + verified, err := verifyRoot(account, root, domain, signature) + errCheck(err, "Failed to verify data") + assert(verified, "Failed to verify") - verified := signature.Verify(root[:], pubKey) - if !verified { - outputIf(!quiet, "Not verified") - os.Exit(_exitFailure) - } - outputIf(!quiet, "Verified") + outputIf(verbose, "Verified") os.Exit(_exitSuccess) }, } +// signatureVerifyAccount obtains the account for the signature verify command. +func signatureVerifyAccount() (e2wtypes.Account, error) { + var account e2wtypes.Account + var err error + if viper.GetString("account") != "" { + ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout")) + defer cancel() + _, account, err = walletAndAccountFromPath(ctx, viper.GetString("account")) + if err != nil { + return nil, errors.Wrap(err, "failed to obtain account") + } + } else { + pubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(signatureVerifySigner, "0x")) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("failed to decode public key %s", signatureVerifySigner)) + } + account, err = util.NewScratchAccount(nil, pubKeyBytes) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("invalid public key %s", signatureVerifySigner)) + } + } + return account, nil +} func init() { signatureCmd.AddCommand(signatureVerifyCmd) signatureFlags(signatureVerifyCmd) diff --git a/cmd/signing.go b/cmd/signing.go index f140bdd..2a94fd4 100644 --- a/cmd/signing.go +++ b/cmd/signing.go @@ -36,6 +36,17 @@ func signStruct(account wtypes.Account, data interface{}, domain []byte) (e2type return signRoot(account, objRoot, domain) } +// verifyStruct verifies the signature of an arbitrary structure. +func verifyStruct(account wtypes.Account, data interface{}, domain []byte, signature e2types.Signature) (bool, error) { + objRoot, err := ssz.HashTreeRoot(data) + outputIf(debug, fmt.Sprintf("Object root is %#x", objRoot)) + if err != nil { + return false, err + } + + return verifyRoot(account, objRoot, domain, signature) +} + // SigningContainer is the container for signing roots with a domain. // Contains SSZ sizes to allow for correct calculation of root. type signingContainer struct { @@ -64,6 +75,21 @@ func signRoot(account wtypes.Account, root [32]byte, domain []byte) (e2types.Sig return sign(account, signingRoot[:]) } +func verifyRoot(account wtypes.Account, root [32]byte, domain []byte, signature e2types.Signature) (bool, error) { + // Build the signing data manually. + container := &signingContainer{ + Root: root[:], + Domain: domain, + } + outputIf(debug, fmt.Sprintf("Signing container:\n root: %#x\n domain: %#x", container.Root, container.Domain)) + signingRoot, err := ssz.HashTreeRoot(container) + if err != nil { + return false, err + } + outputIf(debug, fmt.Sprintf("Signing root: %#x", signingRoot)) + return verify(account, signingRoot[:], signature) +} + func signGeneric(account wtypes.Account, data []byte, domain []byte) (e2types.Signature, error) { alreadyUnlocked, err := unlock(account) if err != nil { @@ -113,6 +139,15 @@ func sign(account wtypes.Account, data []byte) (e2types.Signature, error) { return signature, err } +// verify the signature of arbitrary data. +func verify(account wtypes.Account, data []byte, signature e2types.Signature) (bool, error) { + pubKey, err := bestPublicKey(account) + if err != nil { + return false, errors.Wrap(err, "failed to obtain account public key") + } + return signature.Verify(data, pubKey), nil +} + // unlock attempts to unlock an account. It returns true if the account was already unlocked. func unlock(account e2wtypes.Account) (bool, error) { locker, isAccountLocker := account.(e2wtypes.AccountLocker) diff --git a/cmd/validatordepositdata.go b/cmd/validatordepositdata.go index 831b77e..fe81136 100644 --- a/cmd/validatordepositdata.go +++ b/cmd/validatordepositdata.go @@ -26,7 +26,6 @@ import ( "github.com/wealdtech/ethdo/grpc" e2types "github.com/wealdtech/go-eth2-types/v2" util "github.com/wealdtech/go-eth2-util" - e2wallet "github.com/wealdtech/go-eth2-wallet" string2eth "github.com/wealdtech/go-string2eth" ) @@ -54,12 +53,8 @@ In quiet mode this will return 0 if the the data can be generated correctly, oth defer cancel() assert(validatorDepositDataValidatorAccount != "", "--validatoraccount is required") - validatorWalletName, validatorAccountSpec, err := e2wallet.WalletAndAccountNames(validatorDepositDataValidatorAccount) - errCheck(err, "Failed to obtain wallet and account names") - validatorWallet, err := openNamedWallet(validatorWalletName) - errCheck(err, "Failed to obtain validator wallet") - validatorAccounts, err := accountsFromPath(ctx, validatorWallet, validatorAccountSpec) - errCheck(err, "Failed to obtain validator account") + validatorWallet, validatorAccounts, err := walletAndAccountsFromPath(ctx, validatorDepositDataValidatorAccount) + errCheck(err, "Failed to obtain validator accounts") assert(len(validatorAccounts) > 0, "Failed to obtain validator account") for _, validatorAccount := range validatorAccounts { @@ -72,7 +67,7 @@ In quiet mode this will return 0 if the the data can be generated correctly, oth assert(validatorDepositDataWithdrawalAccount != "" || validatorDepositDataWithdrawalPubKey != "", "--withdrawalaccount or --withdrawalpubkey is required") var withdrawalCredentials []byte if validatorDepositDataWithdrawalAccount != "" { - withdrawalAccount, err := accountFromPath(ctx, validatorDepositDataWithdrawalAccount) + _, withdrawalAccount, err := walletAndAccountFromPath(ctx, validatorDepositDataWithdrawalAccount) errCheck(err, "Failed to obtain withdrawal account") pubKey, err := bestPublicKey(withdrawalAccount) errCheck(err, "Withdrawal account does not provide a public key") diff --git a/cmd/validatorexit.go b/cmd/validatorexit.go index 90971d3..1ef7e01 100644 --- a/cmd/validatorexit.go +++ b/cmd/validatorexit.go @@ -52,19 +52,20 @@ In quiet mode this will return 0 if the transaction has been generated, otherwis err := connect() errCheck(err, "Failed to obtain connect to Ethereum 2 beacon chain node") - exit, signature := validatorExitHandleInput(ctx) - validatorExitHandleExit(ctx, exit, signature) + exit, signature, forkVersion := validatorExitHandleInput(ctx) + validatorExitHandleExit(ctx, exit, signature, forkVersion) os.Exit(_exitSuccess) }, } -func validatorExitHandleInput(ctx context.Context) (*ethpb.VoluntaryExit, e2types.Signature) { +func validatorExitHandleInput(ctx context.Context) (*ethpb.VoluntaryExit, e2types.Signature, []byte) { if validatorExitJSON != "" { return validatorExitHandleJSONInput(validatorExitJSON) } if viper.GetString("account") != "" { - account, err := accountFromPath(ctx, viper.GetString("account")) - errCheck(err, "Failed to access account") + _, account, err := walletAndAccountFromInput(ctx) + errCheck(err, "Failed to obtain account") + outputIf(debug, fmt.Sprintf("Account %s obtained", account.Name())) return validatorExitHandleAccountInput(ctx, account) } if validatorExitKey != "" { @@ -75,10 +76,10 @@ func validatorExitHandleInput(ctx context.Context) (*ethpb.VoluntaryExit, e2type return validatorExitHandleAccountInput(ctx, account) } die("one of --json, --account or --key is required") - return nil, nil + return nil, nil, nil } -func validatorExitHandleJSONInput(input string) (*ethpb.VoluntaryExit, e2types.Signature) { +func validatorExitHandleJSONInput(input string) (*ethpb.VoluntaryExit, e2types.Signature, []byte) { data := &validatorExitData{} err := json.Unmarshal([]byte(input), data) errCheck(err, "Invalid JSON input") @@ -88,10 +89,10 @@ func validatorExitHandleJSONInput(input string) (*ethpb.VoluntaryExit, e2types.S } signature, err := e2types.BLSSignatureFromBytes(data.Signature) errCheck(err, "Invalid signature") - return exit, signature + return exit, signature, data.ForkVersion } -func validatorExitHandleAccountInput(ctx context.Context, account e2wtypes.Account) (*ethpb.VoluntaryExit, e2types.Signature) { +func validatorExitHandleAccountInput(ctx context.Context, account e2wtypes.Account) (*ethpb.VoluntaryExit, e2types.Signature, []byte) { exit := ðpb.VoluntaryExit{} // Beacon chain config required for later work. @@ -137,12 +138,12 @@ func validatorExitHandleAccountInput(ctx context.Context, account e2wtypes.Accou } // TODO fetch current fork version from config (currently using genesis fork version) - currentForkVersion := config["GenesisForkVersion"].([]byte) - outputIf(debug, fmt.Sprintf("Current fork version is %x", currentForkVersion)) + forkVersion := config["GenesisForkVersion"].([]byte) + outputIf(debug, fmt.Sprintf("Current fork version is %x", forkVersion)) genesisValidatorsRoot, err := grpc.FetchGenesisValidatorsRoot(eth2GRPCConn) outputIf(debug, fmt.Sprintf("Genesis validators root is %x", genesisValidatorsRoot)) errCheck(err, "Failed to obtain genesis validators root") - domain := e2types.Domain(e2types.DomainVoluntaryExit, currentForkVersion, genesisValidatorsRoot) + domain := e2types.Domain(e2types.DomainVoluntaryExit, forkVersion, genesisValidatorsRoot) alreadyUnlocked, err := unlock(account) errCheck(err, "Failed to unlock account; please confirm passphrase is correct") @@ -152,16 +153,17 @@ func validatorExitHandleAccountInput(ctx context.Context, account e2wtypes.Accou } errCheck(err, "Failed to sign exit proposal") - return exit, signature + return exit, signature, forkVersion } // validatorExitHandleExit handles the exit request. -func validatorExitHandleExit(ctx context.Context, exit *ethpb.VoluntaryExit, signature e2types.Signature) { +func validatorExitHandleExit(ctx context.Context, exit *ethpb.VoluntaryExit, signature e2types.Signature, forkVersion []byte) { if validatorExitJSONOutput { data := &validatorExitData{ Epoch: exit.Epoch, ValidatorIndex: exit.ValidatorIndex, Signature: signature.Marshal(), + ForkVersion: forkVersion, } res, err := json.Marshal(data) errCheck(err, "Failed to generate JSON") @@ -192,11 +194,12 @@ type validatorExitData struct { Epoch uint64 `json:"epoch"` ValidatorIndex uint64 `json:"validator_index"` Signature []byte `json:"signature"` + ForkVersion []byte `json:"fork_version"` } // MarshalJSON implements custom JSON marshaller. func (d *validatorExitData) MarshalJSON() ([]byte, error) { - return []byte(fmt.Sprintf(`{"epoch":%d,"validator_index":%d,"signature":"%#x"}`, d.Epoch, d.ValidatorIndex, d.Signature)), nil + return []byte(fmt.Sprintf(`{"epoch":%d,"validator_index":%d,"signature":"%#x","fork_version":"%#x"}`, d.Epoch, d.ValidatorIndex, d.Signature, d.ForkVersion)), nil } // UnmarshalJSON implements custom JSON unmarshaller. @@ -242,5 +245,19 @@ func (d *validatorExitData) UnmarshalJSON(data []byte) error { return errors.New("signature missing") } + if val, exists := v["fork_version"]; exists { + forkVersionBytes, ok := val.(string) + if !ok { + return errors.New("fork version invalid") + } + forkVersion, err := hex.DecodeString(strings.TrimPrefix(forkVersionBytes, "0x")) + if err != nil { + return errors.Wrap(err, "fork version invalid") + } + d.ForkVersion = forkVersion + } else { + return errors.New("fork version missing") + } + return nil } diff --git a/cmd/validatorinfo.go b/cmd/validatorinfo.go index c207acd..98b4836 100644 --- a/cmd/validatorinfo.go +++ b/cmd/validatorinfo.go @@ -32,7 +32,6 @@ import ( "github.com/spf13/viper" "github.com/wealdtech/ethdo/grpc" "github.com/wealdtech/ethdo/util" - e2wallet "github.com/wealdtech/go-eth2-wallet" e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2" string2eth "github.com/wealdtech/go-string2eth" ) @@ -128,33 +127,11 @@ In quiet mode this will return 0 if the validator information can be obtained, o // validatorInfoAccount obtains the account for the validator info command. func validatorInfoAccount() (e2wtypes.Account, error) { var account e2wtypes.Account + var err error if viper.GetString("account") != "" { - wallet, err := openWallet() - if err != nil { - return nil, errors.Wrap(err, "failed to open wallet") - } - _, accountName, err := e2wallet.WalletAndAccountNames(viper.GetString("account")) - if err != nil { - return nil, errors.Wrap(err, "failed to obtain account name") - } - - if wallet.Type() == "hierarchical deterministic" && strings.HasPrefix(accountName, "m/") { - assert(getWalletPassphrase() != "", "walletpassphrase is required to obtain information about validators with dynamically generated hierarchical deterministic accounts") - locker, isLocker := wallet.(e2wtypes.WalletLocker) - if isLocker { - ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout")) - defer cancel() - errCheck(locker.Unlock(ctx, []byte(getWalletPassphrase())), "Failed to unlock wallet") - } - } - - accountByNameProvider, isProvider := wallet.(e2wtypes.WalletAccountByNameProvider) - if !isProvider { - return nil, errors.New("failed to ask wallet for account by name") - } ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout")) defer cancel() - account, err = accountByNameProvider.AccountByName(ctx, accountName) + _, account, err = walletAndAccountFromPath(ctx, viper.GetString("account")) if err != nil { return nil, errors.Wrap(err, "failed to obtain account") } diff --git a/cmd/version.go b/cmd/version.go index f63aae9..7fe0b0f 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -22,7 +22,7 @@ import ( "github.com/spf13/viper" ) -var ReleaseVersion = "local build from v1.5.3" +var ReleaseVersion = "local build from v1.5.4" // versionCmd represents the version command var versionCmd = &cobra.Command{ diff --git a/cmd/walletaccounts.go b/cmd/walletaccounts.go index 156d405..b61d5ec 100644 --- a/cmd/walletaccounts.go +++ b/cmd/walletaccounts.go @@ -40,8 +40,8 @@ In quiet mode this will return 0 if the wallet holds any addresses, otherwise 1. assert(viper.GetString("wallet") != "", "wallet is required") - wallet, err := openWallet() - errCheck(err, "Failed to access wallet") + wallet, err := walletFromInput(ctx) + errCheck(err, "Failed to obtain wallet") accounts := make([]e2wtypes.Account, 0, 128) for account := range wallet.Accounts(ctx) { diff --git a/cmd/walletdelete.go b/cmd/walletdelete.go index 2728b8d..4697eb8 100644 --- a/cmd/walletdelete.go +++ b/cmd/walletdelete.go @@ -14,6 +14,7 @@ package cmd import ( + "context" "os" "path/filepath" @@ -34,7 +35,10 @@ In quiet mode this will return 0 if the wallet has been deleted, otherwise 1.`, assert(viper.GetString("remote") == "", "wallet delete not available with remote wallets") assert(viper.GetString("wallet") != "", "--wallet is required") - wallet, err := walletFromPath(viper.GetString("wallet")) + ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout")) + defer cancel() + + wallet, err := walletFromPath(ctx, viper.GetString("wallet")) errCheck(err, "Failed to access wallet") storeProvider, ok := wallet.(wtypes.StoreProvider) diff --git a/cmd/walletexport.go b/cmd/walletexport.go index bef502c..34c1b86 100644 --- a/cmd/walletexport.go +++ b/cmd/walletexport.go @@ -41,7 +41,7 @@ In quiet mode this will return 0 if the wallet is able to be exported, otherwise assert(viper.GetString("wallet") != "", "--wallet is required") assert(walletExportPassphrase != "", "--exportpassphrase is required") - wallet, err := walletFromPath(viper.GetString("wallet")) + wallet, err := walletFromPath(ctx, viper.GetString("wallet")) errCheck(err, "Failed to access wallet") _, ok := wallet.(types.WalletExporter) diff --git a/cmd/walletinfo.go b/cmd/walletinfo.go index 3d48849..fb513a4 100644 --- a/cmd/walletinfo.go +++ b/cmd/walletinfo.go @@ -39,7 +39,7 @@ In quiet mode this will return 0 if the wallet exists, otherwise 1.`, assert(viper.GetString("remote") == "", "wallet info not available with remote wallets") assert(viper.GetString("wallet") != "", "--wallet is required") - wallet, err := walletFromPath(viper.GetString("wallet")) + wallet, err := walletFromPath(ctx, viper.GetString("wallet")) errCheck(err, "unknown wallet") if quiet { diff --git a/docs/usage.md b/docs/usage.md index d4affd9..a8458e1 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -200,11 +200,10 @@ $ ethdo signature sign --data="0x08140077a94642919041503caf5cc1c89c7744a2a08d43c ```sh $ ethdo signature verify --data="0x08140077a94642919041503caf5cc1c89c7744a2a08d43cec91df1795b23ecf2" --signature="0x87c83b31081744667406a11170c5585a11195621d0d3f796bd9006ac4cb5f61c10bf8c5b3014cd4f792b143a644cae100cb3155e8b00a961287bd9e7a5e18cb3b80930708bc9074d11ff47f1e8b9dd0b633e71bcea725fc3e550fdc259c3d130" --account="Personal wallet/Operations" -Verified $ ethdo signature verify --data="0x08140077a94642919041503caf5cc1c89c7744a2a08d43cec91df1795b23ecf2" --signature="0x87c83b31081744667406a11170c5585a11195621d0d3f796bd9006ac4cb5f61c10bf8c5b3014cd4f792b143a644cae100cb3155e8b00a961287bd9e7a5e18cb3b80930708bc9074d11ff47f1e8b9dd0b633e71bcea725fc3e550fdc259c3d130" --account="Personal wallet/Auctions" Not verified $ ethdo signature verify --data="0x08140077a94642919041503caf5cc1c89c7744a2a08d43cec91df1795b23ecf2" --signature="0x89abe2e544ef3eafe397db036103b1d066ba86497f36ed4ab0264162eadc89c7744a2a08d43cec91df128660e70ecbbe11031b4c2e53682d2b91e67b886429bf8fac9bad8c7b63c5f231cc8d66b1377e06e27138b1ddc64b27c6e593e07ebb4b" --signer="0x8e2f9e8cc29658ff37ecc30e95a0807579b224586c185d128cb7a7490784c1ad9b0ab93dbe604ab075b40079931e6670" -$ ethdo signature verify --data="0x08140077a94642919041503caf5cc1c89c7744a2a08d43cec91df1795b23ecf2" --signature="0x87c83b31081744667406a11170c5585a11195621d0d3f796bd9006ac4cb5f61c10bf8c5b3014cd4f792b143a644cae100cb3155e8b00a961287bd9e7a5e18cb3b80930708bc9074d11ff47f1e8b9dd0b633e71bcea725fc3e550fdc259c3d130" --signer="0xad1868210a0cff7aff22633c003c503d4c199c8dcca13bba5b3232fc784d39d3855936e94ce184c3ce27bf15d4347695" +$ ethdo signature verify --data="0x08140077a94642919041503caf5cc1c89c7744a2a08d43cec91df1795b23ecf2" --signature="0x87c83b31081744667406a11170c5585a11195621d0d3f796bd9006ac4cb5f61c10bf8c5b3014cd4f792b143a644cae100cb3155e8b00a961287bd9e7a5e18cb3b80930708bc9074d11ff47f1e8b9dd0b633e71bcea725fc3e550fdc259c3d130" --signer="0xad1868210a0cff7aff22633c003c503d4c199c8dcca13bba5b3232fc784d39d3855936e94ce184c3ce27bf15d4347695" --verbose Verified ``` @@ -308,6 +307,37 @@ Prior justified epoch: 3 Prior justified epoch distance: 4 ``` +### `deposit` comands + +Deposit commands focus on information about deposit data information in a JSON file generated by the `ethdo validator depositdata` command. + +#### `verify` + +`ethdo deposit verify` verifies one or more deposit data information in a JSON file generated by the `ethdo validator depositdata` command. Options include: + - `data`: either a path to the JSON file or the JSON itself + - `withdrawalpubkey`: the public key of the withdrawal for the deposit. If no value is supplied then withdrawal credentials for deposits will not be checked + - `validatorpubkey`: the public key of the validator for the deposit. If no value is supplied then validator public keys will not be checked + - `depositvalue`: the value of the Ether being deposited. If no value is supplied then deposit values will not be checked. + +```sh +$ ethdo deposit verify --data=${HOME}/depositdata.json --withdrawalpubkey=0xad1868210a0cff7aff22633c003c503d4c199c8dcca13bba5b3232fc784d39d3855936e94ce184c3ce27bf15d4347695 --validatorpubkey=0xa951530887ae2494a8cc4f11cf186963b0051ac4f7942375585b9cf98324db1e532a67e521d0fcaab510edad1352394c --depositvalue=32Ether +``` + +### `exit` comands + +Exit commands focus on information about validator exits generated by the `ethdo validator exit` command. + +#### `verify` + +`ethdo exit verify` verifies the validator exit information in a JSON file generated by the `ethdo validator exit` command. Options include: + - `data`: either a path to the JSON file or the JSON itself + - `account`: the account that generated the exit transaction (if available as an account) + - `pubkey`: the public key of the account that generated the exit transaction + +```sh +$ ethdo exit verify --data=${HOME}/exit.json --pubkey=0xa951530887ae2494a8cc4f11cf186963b0051ac4f7942375585b9cf98324db1e532a67e521d0fcaab510edad1352394c +``` + ### `node` commands Node commands focus on information from an Ethereum 2 node.