mirror of
https://github.com/wealdtech/ethdo.git
synced 2026-01-09 14:07:56 -05:00
Add 'exit verify' command
This commit is contained in:
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
32
cmd/exit.go
Normal file
32
cmd/exit.go
Normal file
@@ -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) {
|
||||
}
|
||||
137
cmd/exitverify.go
Normal file
137
cmd/exitverify.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
108
cmd/root.go
108
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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user