mirror of
https://github.com/wealdtech/ethdo.git
synced 2026-01-09 14:07:56 -05:00
146 lines
5.0 KiB
Go
146 lines
5.0 KiB
Go
// 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 (
|
|
"bytes"
|
|
"context"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"strings"
|
|
|
|
eth2client "github.com/attestantio/go-eth2-client"
|
|
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/viper"
|
|
"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 exit data is valid",
|
|
Long: `Verify that exit data generated by "ethdo validator exit" is correct for a given account. For example:
|
|
|
|
ethdo exit verify --data=exitdata.json --account=primary/current
|
|
|
|
In quiet mode this will return 0 if the the exit is verified correctly, otherwise 1.`,
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
ctx := context.Background()
|
|
|
|
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") != "", "exit is required")
|
|
data, err := obtainExitData(viper.GetString("exit"))
|
|
errCheck(err, "Failed to obtain exit data")
|
|
|
|
// Confirm signature is good.
|
|
eth2Client, err := util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
|
errCheck(err, "Failed to connect to Ethereum 2 beacon node")
|
|
|
|
genesis, err := eth2Client.(eth2client.GenesisProvider).Genesis(ctx)
|
|
errCheck(err, "Failed to obtain beacon chain genesis")
|
|
|
|
domain := e2types.Domain(e2types.DomainVoluntaryExit, data.ForkVersion[:], genesis.GenesisValidatorsRoot[:])
|
|
var exitDomain spec.Domain
|
|
copy(exitDomain[:], domain)
|
|
exit := &spec.VoluntaryExit{
|
|
Epoch: data.Exit.Message.Epoch,
|
|
ValidatorIndex: data.Exit.Message.ValidatorIndex,
|
|
}
|
|
exitRoot, err := exit.HashTreeRoot()
|
|
errCheck(err, "Failed to obtain exit hash tree root")
|
|
sig, err := e2types.BLSSignatureFromBytes(data.Exit.Signature[:])
|
|
errCheck(err, "Invalid signature")
|
|
verified, err := util.VerifyRoot(account, exitRoot, exitDomain, sig)
|
|
errCheck(err, "Failed to verify voluntary exit")
|
|
assert(verified, "Voluntary exit failed to verify")
|
|
|
|
fork, err := eth2Client.(eth2client.ForkProvider).Fork(ctx, "head")
|
|
errCheck(err, "Failed to obtain current fork")
|
|
assert(bytes.Equal(data.ForkVersion[:], fork.CurrentVersion[:]) || bytes.Equal(data.ForkVersion[:], fork.PreviousVersion[:]), "Exit is for an old fork version and is no longer valid")
|
|
|
|
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) (*util.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 := &util.ValidatorExitData{}
|
|
err = json.Unmarshal(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("exit", "", "JSON data, or path to JSON data")
|
|
exitVerifyCmd.Flags().StringVar(&exitVerifyPubKey, "pubkey", "", "Public key for which to verify exit")
|
|
}
|
|
|
|
func exitVerifyBindings() {
|
|
if err := viper.BindPFlag("exit", exitVerifyCmd.Flags().Lookup("exit")); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|