mirror of
https://github.com/wealdtech/ethdo.git
synced 2026-01-09 14:07:56 -05:00
236 lines
8.1 KiB
Go
236 lines
8.1 KiB
Go
// Copyright © 2019, 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"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cobra"
|
|
e2types "github.com/wealdtech/go-eth2-types/v2"
|
|
util "github.com/wealdtech/go-eth2-util"
|
|
string2eth "github.com/wealdtech/go-string2eth"
|
|
)
|
|
|
|
type depositData struct {
|
|
Name string `json:"name,omitempty"`
|
|
Account string `json:"account,omitempty"`
|
|
PublicKey string `json:"pubkey"`
|
|
WithdrawalCredentials string `json:"withdrawal_credentials"`
|
|
Signature string `json:"signature"`
|
|
DepositDataRoot string `json:"deposit_data_root"`
|
|
Value uint64 `json:"value"`
|
|
Version uint64 `json:"version"`
|
|
}
|
|
|
|
var depositVerifyData string
|
|
var depositVerifyWithdrawalPubKey string
|
|
var depositVerifyValidatorPubKey string
|
|
var depositVerifyDepositValue string
|
|
|
|
var depositVerifyCmd = &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) {
|
|
assert(depositVerifyData != "", "--data is required")
|
|
deposits, err := depositDataFromJSON(depositVerifyData)
|
|
errCheck(err, "Failed to fetch deposit data")
|
|
|
|
withdrawalCredentials := ""
|
|
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)
|
|
}
|
|
outputIf(debug, fmt.Sprintf("Withdrawal credentials are %s", withdrawalCredentials))
|
|
|
|
depositValue := uint64(0)
|
|
if depositVerifyDepositValue != "" {
|
|
depositValue, err = string2eth.StringToGWei(depositVerifyDepositValue)
|
|
errCheck(err, "Invalid value")
|
|
// This is hard-coded, to allow deposit data to be generated without a connection to the beacon node.
|
|
assert(depositValue >= 1000000000, "deposit value must be at least 1 Ether") // MIN_DEPOSIT_AMOUNT
|
|
}
|
|
|
|
validatorPubKeys := make(map[string]bool)
|
|
if depositVerifyValidatorPubKey != "" {
|
|
validatorPubKeys, err = validatorPubKeysFromInput(depositVerifyValidatorPubKey)
|
|
errCheck(err, "Failed to obtain validator public key(s))")
|
|
}
|
|
|
|
failures := false
|
|
for i, deposit := range deposits {
|
|
if withdrawalCredentials != "" {
|
|
if deposit.WithdrawalCredentials != withdrawalCredentials {
|
|
outputIf(!quiet, fmt.Sprintf("Invalid withdrawal credentials for deposit %d", i))
|
|
failures = true
|
|
}
|
|
}
|
|
if depositValue != 0 {
|
|
if deposit.Value != depositValue {
|
|
outputIf(!quiet, fmt.Sprintf("Invalid deposit value for deposit %d", i))
|
|
failures = true
|
|
}
|
|
}
|
|
if len(validatorPubKeys) != 0 {
|
|
if _, exists := validatorPubKeys[deposit.PublicKey]; !exists {
|
|
outputIf(!quiet, fmt.Sprintf("Unknown validator public key for deposit %d", i))
|
|
failures = true
|
|
}
|
|
}
|
|
outputIf(!quiet, fmt.Sprintf("Deposit %q verified", deposit.Name))
|
|
}
|
|
|
|
if failures {
|
|
os.Exit(_exitFailure)
|
|
}
|
|
os.Exit(_exitSuccess)
|
|
},
|
|
}
|
|
|
|
func validatorPubKeysFromInput(input string) (map[string]bool, error) {
|
|
pubKeys := make(map[string]bool)
|
|
var err error
|
|
var data []byte
|
|
// Input could be a public key or a path to public keys.
|
|
if strings.HasPrefix(input, "0x") {
|
|
// Looks like a public key.
|
|
pubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(input, "0x"))
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "public key is not a hex string")
|
|
}
|
|
if len(pubKeyBytes) != 48 {
|
|
return nil, errors.New("public key should be 48 bytes")
|
|
}
|
|
pubKey, err := e2types.BLSPublicKeyFromBytes(pubKeyBytes)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "invalid public key")
|
|
}
|
|
pubKeys[fmt.Sprintf("%x", pubKey.Marshal())] = true
|
|
} else {
|
|
// Assume it's a path to a file of public keys.
|
|
data, err = ioutil.ReadFile(input)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to find public key file")
|
|
}
|
|
lines := bytes.Split(bytes.Replace(data, []byte("\r\n"), []byte("\n"), -1), []byte("\n"))
|
|
if len(lines) == 0 {
|
|
return nil, errors.New("file has no public keys")
|
|
}
|
|
for _, line := range lines {
|
|
if len(line) == 0 {
|
|
continue
|
|
}
|
|
pubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(string(line), "0x"))
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "public key is not a hex string")
|
|
}
|
|
if len(pubKeyBytes) != 48 {
|
|
return nil, errors.New("public key should be 48 bytes")
|
|
}
|
|
pubKey, err := e2types.BLSPublicKeyFromBytes(pubKeyBytes)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "invalid public key")
|
|
}
|
|
pubKeys[fmt.Sprintf("%x", pubKey.Marshal())] = true
|
|
}
|
|
}
|
|
|
|
return pubKeys, nil
|
|
}
|
|
|
|
func depositDataFromJSON(input string) ([]*depositData, error) {
|
|
var err error
|
|
var data []byte
|
|
// Input could be JSON or a path to JSON
|
|
switch {
|
|
case strings.HasPrefix(input, "{"):
|
|
// Looks like JSON
|
|
data = []byte("[" + input + "]")
|
|
case strings.HasPrefix(input, "["):
|
|
// Looks like JSON array
|
|
data = []byte(input)
|
|
default:
|
|
// 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")
|
|
}
|
|
if data[0] == '{' {
|
|
data = []byte("[" + string(data) + "]")
|
|
}
|
|
}
|
|
var depositData []*depositData
|
|
err = json.Unmarshal(data, &depositData)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "data is not valid JSON")
|
|
}
|
|
if len(depositData) == 0 {
|
|
return nil, errors.New("no deposits supplied")
|
|
}
|
|
minVersion := depositData[0].Version
|
|
maxVersion := depositData[0].Version
|
|
for i := range depositData {
|
|
if depositData[i].PublicKey == "" {
|
|
return nil, fmt.Errorf("no public key for deposit %d", i)
|
|
}
|
|
if depositData[i].DepositDataRoot == "" {
|
|
return nil, fmt.Errorf("no data root for deposit %d", i)
|
|
}
|
|
if depositData[i].Signature == "" {
|
|
return nil, fmt.Errorf("no signature for deposit %d", i)
|
|
}
|
|
if depositData[i].WithdrawalCredentials == "" {
|
|
return nil, fmt.Errorf("no withdrawal credentials for deposit %d", i)
|
|
}
|
|
if depositData[i].Value < 1000000000 {
|
|
return nil, fmt.Errorf("Deposit amount too small for deposit %d", i)
|
|
}
|
|
if depositData[i].Version > maxVersion {
|
|
maxVersion = depositData[i].Version
|
|
}
|
|
if depositData[i].Version < minVersion {
|
|
minVersion = depositData[i].Version
|
|
}
|
|
}
|
|
return depositData, nil
|
|
}
|
|
|
|
func init() {
|
|
depositCmd.AddCommand(depositVerifyCmd)
|
|
depositFlags(depositVerifyCmd)
|
|
depositVerifyCmd.Flags().StringVar(&depositVerifyData, "data", "", "JSON data, or path to JSON data")
|
|
depositVerifyCmd.Flags().StringVar(&depositVerifyWithdrawalPubKey, "withdrawalpubkey", "", "Public key of the account to which the validator funds will be withdrawn")
|
|
depositVerifyCmd.Flags().StringVar(&depositVerifyDepositValue, "depositvalue", "", "Value of the amount to be deposited")
|
|
depositVerifyCmd.Flags().StringVar(&depositVerifyValidatorPubKey, "validatorpubkey", "", "Public key(s) of the account(s) that will be carrying out validation")
|
|
}
|