Compare commits

...

17 Commits

Author SHA1 Message Date
Jim McDonald
0f1c6f09bd Bump version 2020-09-21 09:39:28 +01:00
Jim McDonald
6118f9cab8 Release version 1.5.9 2020-09-21 09:34:32 +01:00
Jim McDonald
829dbd3bf2 fix issue where wallet mnemonics were not normalised to NFKD 2020-09-21 09:33:51 +01:00
Jim McDonald
f0ad10463e Tidy up verbose output 2020-09-07 11:03:35 +01:00
Jim McDonald
3d0dab0b95 block info supports fetching the genesis block 2020-09-07 09:37:03 +01:00
Jim McDonald
5abfabc355 Add attester inclusion command 2020-09-02 11:17:38 +01:00
Jim McDonald
e84b600d5d Add participants to account info; passphrase for account creation is optional for Dirk 2020-08-30 09:38:21 +01:00
Jim McDonald
e64a46f126 Update HOWTO for launchpad 2020-08-26 22:14:59 +01:00
Jim McDonald
0746fa3048 Release 1.5.8 2020-08-25 09:20:45 +01:00
Jim McDonald
94eb3fbca7 Add genesis validators root to chain info 2020-08-23 13:44:17 +01:00
Jim McDonald
48d63398d4 Bump version 2020-08-08 14:25:35 +01:00
Jim McDonald
4e1f47e187 Add ability to create HD accounts with a specific path 2020-08-08 14:25:02 +01:00
Jim McDonald
85c7c7fc55 Bump version 2020-08-02 21:54:43 +01:00
Jim McDonald
05406e8d81 Allow fetching wallet from account variable as well as wallet 2020-08-02 17:02:45 +01:00
Jim McDonald
70d451cea5 Bump version 2020-08-01 16:05:02 +01:00
Jim McDonald
c270d7a2f7 Fix bad path spec for validator deposits 2020-08-01 16:04:38 +01:00
Jim McDonald
115d037948 Add 'exit verify' command 2020-07-31 16:31:06 +01:00
36 changed files with 1154 additions and 460 deletions

View File

@@ -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

11
CHANGELOG.md Normal file
View File

@@ -0,0 +1,11 @@
1.5.9:
- fix issue where wallet mnemonics were not normalised to NFKD
- "block info" supports fetching the gensis block (--slot=0)
- "attester inclusion" command finds the inclusion slot for a validator's attestation
- "account info" with verbose option now displays participants for distributed accounts
- fix issue where distributed account generation without a passphrase was not allowed
1.5.8:
- allow raw deposit transactions to be supplied to "deposit verify"
- move functionality of "account withdrawalcredentials" to be part of "account info"
- add genesis validators root to "chain info"

View File

@@ -17,6 +17,7 @@ import (
"context"
"fmt"
"os"
"regexp"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@@ -33,9 +34,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 +47,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")
}
@@ -59,14 +61,27 @@ In quiet mode this will return 0 if the account is created successfully, otherwi
outputIf(debug, fmt.Sprintf("Distributed account has %d/%d threshold", viper.GetUint32("signing-threshold"), viper.GetUint32("participants")))
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
defer cancel()
account, err = distributedCreator.CreateDistributedAccount(ctx, accountName, viper.GetUint32("participants"), viper.GetUint32("signing-threshold"), []byte(getPassphrase()))
account, err = distributedCreator.CreateDistributedAccount(ctx, accountName, viper.GetUint32("participants"), viper.GetUint32("signing-threshold"), []byte(getOptionalPassphrase()))
} else {
// Want a standard account.
creator, isCreator := wallet.(e2wtypes.WalletAccountCreator)
assert(isCreator, "Wallet does not support account creation")
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
defer cancel()
account, err = creator.CreateAccount(ctx, accountName, []byte(getPassphrase()))
if viper.GetString("path") != "" {
// Want a pathed account
creator, isCreator := wallet.(e2wtypes.WalletPathedAccountCreator)
assert(isCreator, "Wallet does not support account creation with an explicit path")
var match bool
match, err = regexp.Match("^m/[0-9]+/[0-9]+(/[0-9+])+", []byte(viper.GetString("path")))
errCheck(err, "Unable to match path to regular expression")
assert(match, "Path does not match expected format m/...")
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
defer cancel()
account, err = creator.CreatePathedAccount(ctx, viper.GetString("path"), accountName, []byte(getPassphrase()))
} else {
// Want a standard account.
creator, isCreator := wallet.(e2wtypes.WalletAccountCreator)
assert(isCreator, "Wallet does not support account creation")
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
defer cancel()
account, err = creator.CreateAccount(ctx, accountName, []byte(getPassphrase()))
}
}
errCheck(err, "Failed to create account")
@@ -84,11 +99,18 @@ func init() {
accountCmd.AddCommand(accountCreateCmd)
accountFlags(accountCreateCmd)
accountCreateCmd.Flags().Uint32("participants", 0, "Number of participants (for distributed accounts)")
accountCreateCmd.Flags().Uint32("signing-threshold", 0, "Signing threshold (for distributed accounts)")
accountCreateCmd.Flags().String("path", "", "path of account (for hierarchical deterministic accounts)")
}
func accountCreateBindings() {
if err := viper.BindPFlag("participants", accountCreateCmd.Flags().Lookup("participants")); err != nil {
panic(err)
}
accountCreateCmd.Flags().Uint32("signing-threshold", 0, "Signing threshold (for distributed accounts)")
if err := viper.BindPFlag("signing-threshold", accountCreateCmd.Flags().Lookup("signing-threshold")); err != nil {
panic(err)
}
if err := viper.BindPFlag("path", accountCreateCmd.Flags().Lookup("path")); err != nil {
panic(err)
}
}

View File

@@ -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)

View File

@@ -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)
@@ -79,6 +58,13 @@ In quiet mode this will return 0 if the account exists, otherwise 1.`,
if distributedAccount, ok := account.(e2wtypes.DistributedAccount); ok {
fmt.Printf("Composite public key: %#x\n", distributedAccount.CompositePublicKey().Marshal())
fmt.Printf("Signing threshold: %d/%d\n", distributedAccount.SigningThreshold(), len(distributedAccount.Participants()))
if verbose {
fmt.Printf("Participants:\n")
for k, v := range distributedAccount.Participants() {
fmt.Printf(" %d: %s\n", k, v)
}
}
withdrawalPubKey = distributedAccount.CompositePublicKey()
}
if verbose {

View File

@@ -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)

View File

@@ -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")
},
}

View File

@@ -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)

View File

@@ -1,83 +0,0 @@
// 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 (
"context"
"encoding/hex"
"fmt"
"os"
"strings"
"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{
Use: "withdrawalcredentials",
Short: "Provide withdrawal credentials for an account",
Long: `Provide withdrawal credentials for an account. For example:
ethdo account withdrawalcredentials --account="Validators/1"
In quiet mode this will return 0 if the account exists, otherwise 1.`,
Run: func(cmd *cobra.Command, args []string) {
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
defer cancel()
assert(viper.GetString("account") != "" || viper.GetString("pubkey") != "", "account or pubkey is required")
var pubKey []byte
if viper.GetString("pubkey") != "" {
var err error
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)
errCheck(err, "Failed to obtain account")
key, err := bestPublicKey(account)
errCheck(err, "Account does not provide a public key")
pubKey = key.Marshal()
}
if quiet {
os.Exit(_exitSuccess)
}
withdrawalCredentials := util.SHA256(pubKey)
withdrawalCredentials[0] = byte(0) // BLS_WITHDRAWAL_PREFIX
fmt.Printf("%#x\n", withdrawalCredentials)
},
}
func init() {
accountCmd.AddCommand(accountWithdrawalCredentialsCmd)
accountFlags(accountWithdrawalCredentialsCmd)
accountWithdrawalCredentialsCmd.Flags().String("pubkey", "", "Public key (overrides account)")
if err := viper.BindPFlag("pubkey", accountCreateCmd.Flags().Lookup("pubkey")); err != nil {
panic(err)
}
}

32
cmd/attester.go Normal file
View File

@@ -0,0 +1,32 @@
// 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 (
"github.com/spf13/cobra"
)
// attesterCmd represents the attester command
var attesterCmd = &cobra.Command{
Use: "attester",
Short: "Obtain information about Ethereum 2 attesters",
Long: "Obtain information about Ethereum 2 attesters",
}
func init() {
RootCmd.AddCommand(attesterCmd)
}
func attesterFlags(cmd *cobra.Command) {
}

156
cmd/attesterinclusion.go Normal file
View File

@@ -0,0 +1,156 @@
// 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"
"fmt"
"os"
"strings"
"time"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/wealdtech/ethdo/grpc"
"github.com/wealdtech/ethdo/util"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
var attesterInclusionCmd = &cobra.Command{
Use: "inclusion",
Short: "Obtain information about attester inclusion",
Long: `Obtain information about attester inclusion. For example:
ethdo attester inclusion --account=Validators/00001 --epoch=12345
In quiet mode this will return 0 if an attestation from the attester is found on the block fo the given epoch, otherwise 1.`,
Run: func(cmd *cobra.Command, args []string) {
err := connect()
errCheck(err, "Failed to obtain connection to Ethereum 2 beacon chain block")
// Obtain the epoch.
epoch := viper.GetInt64("epoch")
if epoch == -1 {
outputIf(debug, "No epoch supplied; fetching current epoch")
config, err := grpc.FetchChainConfig(eth2GRPCConn)
errCheck(err, "Failed to obtain beacon chain configuration")
slotsPerEpoch := config["SlotsPerEpoch"].(uint64)
secondsPerSlot := config["SecondsPerSlot"].(uint64)
genesisTime, err := grpc.FetchGenesisTime(eth2GRPCConn)
errCheck(err, "Failed to obtain beacon chain genesis")
epoch = int64(time.Since(genesisTime).Seconds()) / int64(secondsPerSlot*slotsPerEpoch)
}
outputIf(debug, fmt.Sprintf("Epoch is %d", epoch))
// Obtain the validator.
account, err := attesterInclusionAccount()
errCheck(err, "Failed to obtain account")
validatorIndex, err := grpc.FetchValidatorIndex(eth2GRPCConn, account)
errCheck(err, "Failed to obtain validator")
// Find the attesting slot for the given epoch.
committees, err := grpc.FetchValidatorCommittees(eth2GRPCConn, uint64(epoch))
errCheck(err, "Failed to obtain validator committees")
slot := uint64(0)
committeeIndex := uint64(0)
validatorPositionInCommittee := uint64(0)
found := false
for searchSlot, committee := range committees {
for searchCommitteeIndex, committeeValidatorIndices := range committee {
for position, committeeValidatorIndex := range committeeValidatorIndices {
if validatorIndex == committeeValidatorIndex {
outputIf(verbose, fmt.Sprintf("Validator %d scheduled to attest at slot %d for epoch %d: entry %d in committee %d", validatorIndex, searchSlot, epoch, position, searchCommitteeIndex))
slot = searchSlot
committeeIndex = uint64(searchCommitteeIndex)
validatorPositionInCommittee = uint64(position)
found = true
break
}
}
}
}
assert(found, "Failed to find attester duty for validator in the given epoch")
startSlot := slot + 1
endSlot := startSlot + 32
for curSlot := startSlot; curSlot < endSlot; curSlot++ {
signedBlock, err := grpc.FetchBlock(eth2GRPCConn, curSlot)
errCheck(err, "Failed to obtain block")
if signedBlock == nil {
outputIf(debug, fmt.Sprintf("No block at slot %d", curSlot))
continue
}
outputIf(debug, fmt.Sprintf("Fetched block %d", curSlot))
for i, attestation := range signedBlock.Block.Body.Attestations {
outputIf(debug, fmt.Sprintf("Attestation %d is for slot %d and committee %d", i, attestation.Data.Slot, attestation.Data.CommitteeIndex))
if attestation.Data.Slot == slot &&
attestation.Data.CommitteeIndex == committeeIndex &&
attestation.AggregationBits.BitAt(validatorPositionInCommittee) {
if verbose {
fmt.Printf("Attestation included in block %d, attestation %d (inclusion delay %d)\n", curSlot, i, curSlot-slot)
} else if !quiet {
fmt.Printf("Attestation included in block %d (inclusion delay %d)\n", curSlot, curSlot-slot)
}
os.Exit(_exitSuccess)
}
}
}
outputIf(verbose, "Attestation not included on the chain")
os.Exit(_exitFailure)
},
}
// attesterInclusionAccount obtains the account for the attester inclusion command.
func attesterInclusionAccount() (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 {
pubKey := viper.GetString("pubkey")
pubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(pubKey, "0x"))
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("failed to decode public key %s", pubKey))
}
account, err = util.NewScratchAccount(nil, pubKeyBytes)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("invalid public key %s", pubKey))
}
}
return account, nil
}
func init() {
attesterCmd.AddCommand(attesterInclusionCmd)
attesterFlags(attesterInclusionCmd)
attesterInclusionCmd.Flags().Int64("epoch", -1, "the current epoch")
attesterInclusionCmd.Flags().String("pubkey", "", "the public key of the attester")
}
func attesterInclusionBindings() {
if err := viper.BindPFlag("epoch", attesterInclusionCmd.Flags().Lookup("epoch")); err != nil {
panic(err)
}
if err := viper.BindPFlag("pubkey", attesterInclusionCmd.Flags().Lookup("pubkey")); err != nil {
panic(err)
}
}

View File

@@ -54,7 +54,6 @@ In quiet mode this will return 0 if the block information is present and not ski
genesisTime, err := grpc.FetchGenesisTime(eth2GRPCConn)
errCheck(err, "Failed to obtain beacon chain genesis")
assert(blockInfoStream || blockInfoSlot != 0, "--slot or --stream is required")
assert(!blockInfoStream || blockInfoSlot == -1, "--slot and --stream are not supported together")
var slot uint64
@@ -64,8 +63,6 @@ In quiet mode this will return 0 if the block information is present and not ski
} else {
slot = uint64(blockInfoSlot)
}
assert(slot > 0, "slot must be greater than 0")
signedBlock, err := grpc.FetchBlock(eth2GRPCConn, slot)
errCheck(err, "Failed to obtain block")
if signedBlock == nil {

View File

@@ -39,6 +39,9 @@ In quiet mode this will return 0 if the chain information can be obtained, other
genesisTime, err := grpc.FetchGenesisTime(eth2GRPCConn)
errCheck(err, "Failed to obtain genesis time")
genesisValidatorsRoot, err := grpc.FetchGenesisValidatorsRoot(eth2GRPCConn)
errCheck(err, "Failed to obtain genesis validators root")
if quiet {
os.Exit(_exitSuccess)
}
@@ -49,9 +52,10 @@ In quiet mode this will return 0 if the chain information can be obtained, other
fmt.Printf("Genesis time: %s\n", genesisTime.Format(time.UnixDate))
outputIf(verbose, fmt.Sprintf("Genesis timestamp: %v", genesisTime.Unix()))
}
outputIf(verbose, fmt.Sprintf("Genesis fork version: %0x", config["GenesisForkVersion"].([]byte)))
outputIf(verbose, fmt.Sprintf("Seconds per slot: %v", config["SecondsPerSlot"].(uint64)))
outputIf(verbose, fmt.Sprintf("Slots per epoch: %v", config["SlotsPerEpoch"].(uint64)))
fmt.Printf("Genesis validators root: %#x\n", genesisValidatorsRoot)
fmt.Printf("Genesis fork version: %#x\n", config["GenesisForkVersion"].([]byte))
fmt.Printf("Seconds per slot: %d\n", config["SecondsPerSlot"].(uint64))
fmt.Printf("Slots per epoch: %d\n", config["SlotsPerEpoch"].(uint64))
os.Exit(_exitSuccess)
},

View File

@@ -16,72 +16,82 @@ package cmd
import (
"bytes"
"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/wealdtech/ethdo/util"
e2types "github.com/wealdtech/go-eth2-types/v2"
util "github.com/wealdtech/go-eth2-util"
eth2util "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 depositVerifyDepositAmount string
var depositVerifyCmd = &cobra.Command{
Use: "verify",
Short: "Verify deposit data matches requirements",
Long: `Verify deposit data matches requirements. For example:
Short: "Verify deposit data matches the provided data",
Long: `Verify deposit data matches the provided input data. 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.
The deposit data is compared to the supplied withdrawal account/public key, validator public key, and value to ensure they match.
In quiet mode this will return 0 if the the data can be generated correctly, otherwise 1.`,
In quiet mode this will return 0 if the the data is verified correctly, otherwise 1.`,
Run: func(cmd *cobra.Command, args []string) {
assert(depositVerifyData != "", "--data is required")
deposits, err := depositDataFromJSON(depositVerifyData)
var data []byte
var err error
// Input could be JSON or a path to JSON.
switch {
case strings.HasPrefix(depositVerifyData, "0x"):
// Looks like raw binary.
data = []byte(depositVerifyData)
case strings.HasPrefix(depositVerifyData, "{"):
// Looks like JSON.
data = []byte("[" + depositVerifyData + "]")
case strings.HasPrefix(depositVerifyData, "["):
// Looks like JSON array.
data = []byte(depositVerifyData)
default:
// Assume it's a path to JSON.
data, err = ioutil.ReadFile(depositVerifyData)
errCheck(err, "Failed to read deposit data file")
if data[0] == '{' {
data = []byte("[" + string(data) + "]")
}
}
deposits, err := util.DepositInfoFromJSON(data)
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 = eth2util.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 != "" {
depositValue, err = string2eth.StringToGWei(depositVerifyDepositValue)
depositAmount := uint64(0)
if depositVerifyDepositAmount != "" {
depositAmount, err = string2eth.StringToGWei(depositVerifyDepositAmount)
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
assert(depositAmount >= 1000000000, "deposit amount 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,25 +99,17 @@ 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 {
outputIf(!quiet, fmt.Sprintf("Invalid withdrawal credentials for deposit %d", i))
failures = true
}
if deposit.Amount == 0 {
deposit.Amount = depositAmount
}
if depositValue != 0 {
if deposit.Value != depositValue {
outputIf(!quiet, fmt.Sprintf("Invalid deposit value for deposit %d", i))
failures = true
}
verified, err := verifyDeposit(deposit, withdrawalCredentials, validatorPubKeys, depositAmount)
errCheck(err, fmt.Sprintf("Error attempting to verify deposit %d", i))
if !verified {
failures = true
outputIf(!quiet, fmt.Sprintf("Deposit %q failed verification", deposit.Name))
} else {
outputIf(quiet, fmt.Sprintf("Deposit %q verified", deposit.Name))
}
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 {
@@ -117,8 +119,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 +137,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,68 +165,56 @@ 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
}
}
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) + "]")
func verifyDeposit(deposit *util.DepositInfo, withdrawalCredentials []byte, validatorPubKeys map[[48]byte]bool, amount uint64) (bool, error) {
if withdrawalCredentials != nil {
if !bytes.Equal(deposit.WithdrawalCredentials, withdrawalCredentials) {
return false, errors.New("withdrawal credentials incorrect")
}
outputIf(verbose, "Withdrawal credentials verified")
}
var depositData []*depositData
err = json.Unmarshal(data, &depositData)
if amount != 0 {
if deposit.Amount != amount {
return false, errors.New("deposit value incorrect")
}
outputIf(verbose, "Amount verified")
}
if len(validatorPubKeys) != 0 {
var key [48]byte
copy(key[:], deposit.PublicKey)
if _, exists := validatorPubKeys[key]; !exists {
return false, errors.New("validator public key incorrect")
}
outputIf(verbose, "Validator public key verified")
}
depositData := &ethpb.Deposit_Data{
PublicKey: deposit.PublicKey,
WithdrawalCredentials: deposit.WithdrawalCredentials,
Amount: deposit.Amount,
Signature: deposit.Signature,
}
depositDataRoot, err := depositData.HashTreeRoot()
if err != nil {
return nil, errors.Wrap(err, "data is not valid JSON")
return false, errors.Wrap(err, "failed to generate deposit data root")
}
if len(depositData) == 0 {
return nil, errors.New("no deposits supplied")
if !bytes.Equal(deposit.DepositDataRoot, depositDataRoot[:]) {
return false, errors.New("deposit data root incorrect")
}
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
outputIf(debug, "Deposit data root verified")
outputIf(verbose, "Deposit verified")
return true, nil
}
func init() {
@@ -230,6 +222,6 @@ func init() {
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(&depositVerifyDepositAmount, "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")
}

32
cmd/exit.go Normal file
View 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) {
}

138
cmd/exitverify.go Normal file
View File

@@ -0,0 +1,138 @@
// 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 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, 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 := &ethpb.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")
}
func exitVerifyBindings() {
if err := viper.BindPFlag("data", exitVerifyCmd.Flags().Lookup("data")); err != nil {
panic(err)
}
}

View File

@@ -37,3 +37,13 @@ func getPassphrase() string {
assert(len(passphrases) == 1, "multiple passphrases supplied; cannot continue")
return passphrases[0]
}
// getOptionalPassphrase fetches the passphrase if supplied by the user.
func getOptionalPassphrase() string {
passphrases := getPassphrases()
if len(passphrases) == 0 {
return ""
}
assert(len(passphrases) == 1, "multiple passphrases supplied; cannot continue")
return passphrases[0]
}

View File

@@ -72,11 +72,22 @@ func persistentPreRun(cmd *cobra.Command, args []string) {
return
}
// We bind viper here so that we bind to the correct command
// We bind viper here so that we bind to the correct command.
quiet = viper.GetBool("quiet")
verbose = viper.GetBool("verbose")
debug = viper.GetBool("debug")
rootStore = viper.GetString("store")
// Command-specific bindings.
switch fmt.Sprintf("%s/%s", cmd.Parent().Name(), cmd.Name()) {
case "account/create":
accountCreateBindings()
case "attester/inclusion":
attesterInclusionBindings()
case "exit/verify":
exitVerifyBindings()
case "wallet/create":
walletCreateBindings()
}
if quiet && verbose {
fmt.Println("Cannot supply both quiet and verbose flags")
@@ -233,12 +244,36 @@ func outputIf(condition bool, msg string) {
}
}
// walletFromInput obtains a wallet given the information in the viper variable
// "account", or if not present the viper variable "wallet".
func walletFromInput(ctx context.Context) (e2wtypes.Wallet, error) {
if viper.GetString("account") != "" {
return walletFromPath(ctx, viper.GetString("account"))
}
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 +284,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 +310,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,15 +318,26 @@ 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) {
accounts := make([]e2wtypes.Account, 0)
// 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")
}
_, accountSpec, err := e2wallet.WalletAndAccountNames(path)
if err != nil {
return nil, nil, errors.Wrap(err, "failed to obtain account specification")
}
if accountSpec == "" {
accountSpec = "^.*$"
} else {
@@ -294,6 +345,7 @@ func accountsFromPath(ctx context.Context, wallet e2wtypes.Wallet, accountSpec s
}
re := regexp.MustCompile(accountSpec)
accounts := make([]e2wtypes.Account, 0)
for account := range wallet.Accounts(ctx) {
if re.Match([]byte(account.Name())) {
accounts = append(accounts, account)
@@ -305,7 +357,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 +421,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")

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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")

View File

@@ -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 := &ethpb.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
}

View File

@@ -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")
}

View File

@@ -22,7 +22,7 @@ import (
"github.com/spf13/viper"
)
var ReleaseVersion = "local build from v1.5.3"
var ReleaseVersion = "local build from v1.5.9"
// versionCmd represents the version command
var versionCmd = &cobra.Command{

View File

@@ -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) {

View File

@@ -28,6 +28,7 @@ import (
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
hd "github.com/wealdtech/go-eth2-wallet-hd/v2"
nd "github.com/wealdtech/go-eth2-wallet-nd/v2"
"golang.org/x/text/unicode/norm"
)
var walletCreateCmd = &cobra.Command{
@@ -106,6 +107,9 @@ func walletCreateHD(ctx context.Context, name string, passphrase string, mnemoni
mnemonicPassphrase = strings.Join(mnemonicParts[24:], " ")
}
}
// Normalise the input.
mnemonic = string(norm.NFKD.Bytes([]byte(mnemonic)))
mnemonicPassphrase = string(norm.NFKD.Bytes([]byte(mnemonicPassphrase)))
// Ensure the mnemonic is valid
if !bip39.IsMnemonicValid(mnemonic) {
@@ -135,10 +139,13 @@ func init() {
walletCmd.AddCommand(walletCreateCmd)
walletFlags(walletCreateCmd)
walletCreateCmd.Flags().String("type", "non-deterministic", "Type of wallet to create (non-deterministic or hierarchical deterministic)")
walletCreateCmd.Flags().String("mnemonic", "", "The 24-word mnemonic for a hierarchical deterministic wallet")
}
func walletCreateBindings() {
if err := viper.BindPFlag("type", walletCreateCmd.Flags().Lookup("type")); err != nil {
panic(err)
}
walletCreateCmd.Flags().String("mnemonic", "", "The 24-word mnemonic for a hierarchical deterministic wallet")
if err := viper.BindPFlag("mnemonic", walletCreateCmd.Flags().Lookup("mnemonic")); err != nil {
panic(err)
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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 {

View File

@@ -65,3 +65,21 @@ ethdo wallet info --verbose --wallet="My wallet"
```
This will provide, amongst other information, a `Location` line giving the directory where the wallet information resides.
## Recreate launchpad wallet and accounts
Recreating launchpad accounts requires two steps: recreating the wallet, and recreating the individual accounts. All that is required is the mnemonic from the launchpad process.
To recreate the wallet with the given mnemonic run the following command (changing the wallet name, passphrase and mnemonic as required):
```sh
ethdo wallet create --wallet="Launchpad" --type=hd --walletpassphrase=walletsecret --mnemonic="faculty key lamp panel appear choose express off absent dance strike twenty elephant expect swift that resist bicycle kind sun favorite evoke engage thumb"
```
Launchpad accounts are identified by their path. The path can be seen in the filename of the keystore, for example the filename `keystore-m_12381_3600_1_0_0-1596891358.json` relates to a path of `m/12381/3600/1/0/0`. It is also present directly in the keystore under the `path` key.
To create an account corresponding to this key with the account name "Account 1" you would use the command:
```sh
ethdo account create --account="Launchpad/Account 1" --walletpassphrase=walletsecret --passphrase=secret --path=m/12381/3600/1/0/0
```

View File

@@ -112,9 +112,12 @@ Account commands focus on information about local accounts, generally those used
`ethdo account create` creates a new account with the given parameters. Options for creating an account include:
- `account`: the name of the account to create
- `passphrase`: the passphrase for the account
- `path`: the HD path for the account (only for hierarchical deterministic accounts)
Note that for hierarchical deterministic wallets you will also need to supply `--walletpassphrase` to unlock the wallet seed.
For distributed accounts you will also need to supply `--participants` and `--signing-threshold`.
```sh
$ ethdo account create --account="Personal wallet/Operations" --walletpassphrase="my wallet secret" --passphrase="my account secret"
```
@@ -200,11 +203,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 +310,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, the JSON itself, or a hex string representing a deposit transaction
- `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.
@@ -333,6 +366,7 @@ Current slot: 178
Current epoch: 5
Genesis timestamp: 1587020563
```
### `validator` commands
Validator commands focus on interaction with Ethereum 2 validators.
@@ -398,6 +432,22 @@ Balance: 3.201850307 Ether
Effective balance: 3.1 Ether
```
### `attester` commands
Attester commands focus on Ethereum 2 validators' actions as attesters.
#### `inclusion`
`ethdo attester inclusion` finds the block with wihch an attestation is included on the chain. Options include:
- `epoch` the epoch in which to obtain the inclusion information (defaults to current epoch)
- `account` the account for which to fetch the inclusion information
- `pubkey` the public key for which to fetch the inclusion information
```sh
$ ethdo attester inclusion --account=Validators/1 --epoch=6484
Attestation included in block 207492 (inclusion delay 1)
```
## Maintainers
Jim McDonald: [@mcdee](https://github.com/mcdee).

31
go.mod
View File

@@ -4,45 +4,48 @@ go 1.13
require (
github.com/OneOfOne/xxhash v1.2.5 // indirect
github.com/aws/aws-sdk-go v1.33.11 // indirect
github.com/aws/aws-sdk-go v1.34.9 // indirect
github.com/ferranbt/fastssz v0.0.0-20200818222714-826c7ef45b30 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/gogo/protobuf v1.3.1
github.com/google/uuid v1.1.1
github.com/grpc-ecosystem/grpc-gateway v1.14.6 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.14.7 // indirect
github.com/herumi/bls-eth-go-binary v0.0.0-20200722032157-41fc56eba7b4
github.com/mitchellh/go-homedir v1.1.0
github.com/mitchellh/mapstructure v1.3.3 // indirect
github.com/pelletier/go-toml v1.8.0 // indirect
github.com/pkg/errors v0.9.1
github.com/prysmaticlabs/ethereumapis v0.0.0-20200709024211-e8095222f77b
github.com/prysmaticlabs/ethereumapis v0.0.0-20200812153649-a842fc47c2c3
github.com/prysmaticlabs/go-bitfield v0.0.0-20200618145306-2ae0807bef65
github.com/prysmaticlabs/go-ssz v0.0.0-20200612203617-6d5c9aa213ae
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/afero v1.3.2 // indirect
github.com/spf13/afero v1.3.4 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/cobra v1.0.0
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.7.0
github.com/spf13/viper v1.7.1
github.com/tyler-smith/go-bip39 v1.0.2
github.com/wealdtech/eth2-signer-api v1.5.2
github.com/wealdtech/go-bytesutil v1.1.1
github.com/wealdtech/go-ecodec v1.1.0
github.com/wealdtech/go-eth2-types/v2 v2.5.0
github.com/wealdtech/go-eth2-util v1.5.0
github.com/wealdtech/go-eth2-wallet v1.12.0
github.com/wealdtech/go-eth2-wallet-dirk v1.0.1
github.com/wealdtech/go-eth2-wallet v1.13.0
github.com/wealdtech/go-eth2-wallet-dirk v1.0.2
github.com/wealdtech/go-eth2-wallet-distributed v1.1.0
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.1.0
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.3.0
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.4.0
github.com/wealdtech/go-eth2-wallet-nd/v2 v2.3.0
github.com/wealdtech/go-eth2-wallet-store-filesystem v1.16.1
github.com/wealdtech/go-eth2-wallet-store-s3 v1.8.0
github.com/wealdtech/go-eth2-wallet-types/v2 v2.6.0
github.com/wealdtech/go-eth2-wallet-types/v2 v2.7.0
github.com/wealdtech/go-string2eth v1.1.0
golang.org/x/sys v0.0.0-20200722175500-76b94024e4b6 // indirect
golang.org/x/text v0.3.3 // indirect
google.golang.org/genproto v0.0.0-20200722002428-88e341933a54 // indirect
google.golang.org/grpc v1.30.0
gopkg.in/ini.v1 v1.57.0 // indirect
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect
golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect
golang.org/x/sys v0.0.0-20200821140526-fda516888d29 // indirect
golang.org/x/text v0.3.3
google.golang.org/genproto v0.0.0-20200815001618-f69a88009b70 // indirect
google.golang.org/grpc v1.31.0
gopkg.in/ini.v1 v1.60.1 // indirect
)

47
go.sum
View File

@@ -31,6 +31,10 @@ github.com/aws/aws-sdk-go v1.33.5 h1:p2fr1ryvNTU6avUWLI+/H7FGv0TBIjzVM5WDgXBBv4U
github.com/aws/aws-sdk-go v1.33.5/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aws/aws-sdk-go v1.33.11 h1:A7b3mNKbh/0zrhnNN/KxWD0YZJw2RImnjFXWOquYKB4=
github.com/aws/aws-sdk-go v1.33.11/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aws/aws-sdk-go v1.34.5 h1:FwubVVX9u+kW9qDCjVzyWOdsL+W5wPq683wMk2R2GXk=
github.com/aws/aws-sdk-go v1.34.5/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aws/aws-sdk-go v1.34.9 h1:cUGBW9CVdi0mS7K1hDzxIqTpfeWhpoQiguq81M1tjK0=
github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
@@ -67,6 +71,11 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/ferranbt/fastssz v0.0.0-20200514094935-99fccaf93472 h1:maoKvILdMk6CSWHanFcUdxXIZGKD9YpWIaVbUQ/4kfg=
github.com/ferranbt/fastssz v0.0.0-20200514094935-99fccaf93472/go.mod h1:LlFXPmgrgVYsuoFDwV8rDJ9tvt1pLQdjKvU1b5IRES0=
github.com/ferranbt/fastssz v0.0.0-20200728110133-0b6e349af87a/go.mod h1:DyEu2iuLBnb/T51BlsiO3yLYdJC6UbGMrIkqK1KmQxM=
github.com/ferranbt/fastssz v0.0.0-20200803113354-a18be873a4b6 h1:P440pnxSHIQI8gg7lQNNo+/F88EzUzS3Os0Xkhta7OQ=
github.com/ferranbt/fastssz v0.0.0-20200803113354-a18be873a4b6/go.mod h1:DyEu2iuLBnb/T51BlsiO3yLYdJC6UbGMrIkqK1KmQxM=
github.com/ferranbt/fastssz v0.0.0-20200818222714-826c7ef45b30 h1:Sbm5yhPObRc+RCZa9fAR/Px/j1qYwuqawF0X8dR2gAw=
github.com/ferranbt/fastssz v0.0.0-20200818222714-826c7ef45b30/go.mod h1:DyEu2iuLBnb/T51BlsiO3yLYdJC6UbGMrIkqK1KmQxM=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
@@ -126,6 +135,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t
github.com/grpc-ecosystem/grpc-gateway v1.13.0/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c=
github.com/grpc-ecosystem/grpc-gateway v1.14.6 h1:8ERzHx8aj1Sc47mu9n/AksaKCSWrMchFtkdrS4BIj5o=
github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw=
github.com/grpc-ecosystem/grpc-gateway v1.14.7 h1:Nk5kuHrnWUTf/0GL1a/vchH/om9Ap2/HnVna+jYZgTY=
github.com/grpc-ecosystem/grpc-gateway v1.14.7/go.mod h1:oYZKL012gGh6LMyg/xA7Q2yq6j8bu0wa+9w14EEthWU=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -243,6 +254,8 @@ github.com/prysmaticlabs/ethereumapis v0.0.0-20200619200018-174e3b90d786 h1:bJiO
github.com/prysmaticlabs/ethereumapis v0.0.0-20200619200018-174e3b90d786/go.mod h1:rs05kpTfWKl0KflsBWzBQFstoyPFMTWQTbxSAyGHe78=
github.com/prysmaticlabs/ethereumapis v0.0.0-20200709024211-e8095222f77b h1:GjYix8Y4VpQhlsjA2ickr3HxjIns4bI36zOmC+lwaNw=
github.com/prysmaticlabs/ethereumapis v0.0.0-20200709024211-e8095222f77b/go.mod h1:rs05kpTfWKl0KflsBWzBQFstoyPFMTWQTbxSAyGHe78=
github.com/prysmaticlabs/ethereumapis v0.0.0-20200812153649-a842fc47c2c3 h1:0f++UXRfp4/Mrmlfj3UaCnYj2lPr6El0gWWTBb9MD2Y=
github.com/prysmaticlabs/ethereumapis v0.0.0-20200812153649-a842fc47c2c3/go.mod h1:k7b2dxy6RppCG6kmOJkNOXzRpEoTdsPygc2aQhsUsZk=
github.com/prysmaticlabs/go-bitfield v0.0.0-20191017011753-53b773adde52/go.mod h1:hCwmef+4qXWjv0jLDbQdWnL0Ol7cS7/lCSS26WR+u6s=
github.com/prysmaticlabs/go-bitfield v0.0.0-20200322041314-62c2aee71669/go.mod h1:hCwmef+4qXWjv0jLDbQdWnL0Ol7cS7/lCSS26WR+u6s=
github.com/prysmaticlabs/go-bitfield v0.0.0-20200618145306-2ae0807bef65 h1:hJfAWrlxx7SKpn4S/h2JGl2HHwA1a2wSS3HAzzZ0F+U=
@@ -275,6 +288,8 @@ github.com/spf13/afero v1.3.0 h1:Ysnmjh1Di8EaWaBv40CYR4IdaIsBc5996Gh1oZzCBKk=
github.com/spf13/afero v1.3.0/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
github.com/spf13/afero v1.3.2 h1:GDarE4TJQI52kYSbSAmLiId1Elfj+xgSDqrUZxFhxlU=
github.com/spf13/afero v1.3.2/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
github.com/spf13/afero v1.3.4 h1:8q6vk3hthlpb2SouZcnBVKboxWQWMDNF38bwholZrJc=
github.com/spf13/afero v1.3.4/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
@@ -289,6 +304,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@@ -334,10 +351,14 @@ github.com/wealdtech/go-eth2-wallet v1.11.0 h1:2KfrWDqF4sWGgk4N5+DaYmh0hOnqiCl0P
github.com/wealdtech/go-eth2-wallet v1.11.0/go.mod h1:E9ZRNO4JNdi27ys7oc+xWWucXu4IGfV5q1vWC9X3oqg=
github.com/wealdtech/go-eth2-wallet v1.12.0 h1:nrwI3jPhehUhJGlBtNv/UmIo/57llvuVZZavLnfdQHI=
github.com/wealdtech/go-eth2-wallet v1.12.0/go.mod h1:ouV+YSMbzk2dyecmofm8jhaMKdSigdIPMSnSqmWEfW8=
github.com/wealdtech/go-eth2-wallet v1.13.0 h1:ayy/jBcKVZhqU4OMlZDieRzhwSiF3Ozmc3e3LrdpWro=
github.com/wealdtech/go-eth2-wallet v1.13.0/go.mod h1:O4efkfrSBRRa7Q8vZQ8usUCBRki+/zE531b3JqScIII=
github.com/wealdtech/go-eth2-wallet-dirk v1.0.0 h1:1QUcWILF3h4OLCgTPpWklvRSuPu0fqrt15jwSm7CSC4=
github.com/wealdtech/go-eth2-wallet-dirk v1.0.0/go.mod h1:VTzjJ51dedvYPr4huI7g7KXZVTpGR6ZrCDQwBxJpLck=
github.com/wealdtech/go-eth2-wallet-dirk v1.0.1 h1:YUE1QlJPun8b+xbz0JM71/3t1i9zp9KjcZdJvtJQL+E=
github.com/wealdtech/go-eth2-wallet-dirk v1.0.1/go.mod h1:5jK/aEAjYAVRBKKjYAvJWSmOWxiECs4asYXHwloNI+w=
github.com/wealdtech/go-eth2-wallet-dirk v1.0.2 h1:ZxAdF6iTOzYHtQlWd1nzVevZ+HtXS/LLn580t+NXT3A=
github.com/wealdtech/go-eth2-wallet-dirk v1.0.2/go.mod h1:5jK/aEAjYAVRBKKjYAvJWSmOWxiECs4asYXHwloNI+w=
github.com/wealdtech/go-eth2-wallet-distributed v1.0.1 h1:3BxMII8T6t16g6lWcYWXjfdvaw8rXuwMQx9h0TG5wRg=
github.com/wealdtech/go-eth2-wallet-distributed v1.0.1/go.mod h1:Ha/8S+SCLEuSfXHdvhTLwnKaEF47o6gzQ+FURKwftvU=
github.com/wealdtech/go-eth2-wallet-distributed v1.1.0 h1:OZjjuxcIYo+EhAfph7lYP1z+VeNs9ruOI32kqtYe1Jg=
@@ -351,6 +372,8 @@ github.com/wealdtech/go-eth2-wallet-hd/v2 v2.2.0 h1:L+yrAn8TC9DQUw+S7moOJxQTp2jr
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.2.0/go.mod h1:lhSwtkIO/Pfg5kz8k50yrDgj7ZQaElCPsXnixlrQn/I=
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.3.0 h1:UORXUYRoUYgYF96Y+QiBq33OKQVtn/nEjnSoQbe1UOA=
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.3.0/go.mod h1:Kc/8WcqMTczfH2xy5mDfCRd0NI/ca/j2jXmqJ7gz8yk=
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.4.0 h1:bqG/i1fpDpnQFgJ+NBLgOLdmGhXiinwlrFVD1OAgZlo=
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.4.0/go.mod h1:xBbzc+aRD06dTL5phnMshpM4sryyWYM8m86sjQKG5u0=
github.com/wealdtech/go-eth2-wallet-nd/v2 v2.1.2/go.mod h1:IssxoHII0ewO1VysMfCmdJP1D00tRhRhXIhhaEXIOVE=
github.com/wealdtech/go-eth2-wallet-nd/v2 v2.2.0 h1:h4eePfG0ANOJYMonmIYOvxJ9uLmBEX4APb2O8Vhtv6k=
github.com/wealdtech/go-eth2-wallet-nd/v2 v2.2.0/go.mod h1:Un2EtseZWSObmTBjgkt7Qz2am54S/0115jrF83lto1U=
@@ -380,6 +403,8 @@ github.com/wealdtech/go-eth2-wallet-types/v2 v2.5.0 h1:J29mbkSCUMl2xdu8Lg6U+JptF
github.com/wealdtech/go-eth2-wallet-types/v2 v2.5.0/go.mod h1:X9kYUH/E5YMqFMZ4xL6MJanABUkJGaH/yPZRT2o+yYA=
github.com/wealdtech/go-eth2-wallet-types/v2 v2.6.0 h1:vBrH5icPPSeb14cdShA7/P2PBZOgZscJ2IhBlTIaFrA=
github.com/wealdtech/go-eth2-wallet-types/v2 v2.6.0/go.mod h1:X9kYUH/E5YMqFMZ4xL6MJanABUkJGaH/yPZRT2o+yYA=
github.com/wealdtech/go-eth2-wallet-types/v2 v2.7.0 h1:pquFQdIWEiSYrpIpFuvsRuialI8t9KhFsPvbIBPnzic=
github.com/wealdtech/go-eth2-wallet-types/v2 v2.7.0/go.mod h1:X9kYUH/E5YMqFMZ4xL6MJanABUkJGaH/yPZRT2o+yYA=
github.com/wealdtech/go-indexer v1.0.0 h1:/S4rfWQbSOnnYmwnvuTVatDibZ8o1s9bmTCHO16XINg=
github.com/wealdtech/go-indexer v1.0.0/go.mod h1:u1cjsbsOXsm5jzJDyLmZY7GsrdX8KYXKBXkZcAmk3Zg=
github.com/wealdtech/go-string2eth v1.1.0 h1:USJQmysUrBYYmZs7d45pMb90hRSyEwizP7lZaOZLDAw=
@@ -406,6 +431,10 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnk
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -446,6 +475,10 @@ golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYc
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc h1:zK/HqS5bZxDptfPJNq8v7vJfXtkU7r9TLIoSr1bXaP4=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -478,6 +511,10 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORK
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200722175500-76b94024e4b6 h1:X9xIZ1YU8bLZA3l6gqDUHSFiD0GFI9S548h6C8nDtOY=
golang.org/x/sys v0.0.0-20200722175500-76b94024e4b6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed h1:J22ig1FUekjjkmZUM7pTKixYm8DvrYsvrBZdunYeIuQ=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200821140526-fda516888d29 h1:mNuhGagCf3lDDm5C0376C/sxh6V7fy9WbdEu/YDNA04=
golang.org/x/sys v0.0.0-20200821140526-fda516888d29/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@@ -540,6 +577,10 @@ google.golang.org/genproto v0.0.0-20200715011427-11fb19a81f2c h1:6DWnZZ6EY/59QRR
google.golang.org/genproto v0.0.0-20200715011427-11fb19a81f2c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200722002428-88e341933a54 h1:ASrBgpl9XvkNTP0m39/j18mid7aoF21npu2ioIBxYnY=
google.golang.org/genproto v0.0.0-20200722002428-88e341933a54/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200814021100-8c09557e8a18 h1:hjnc4GP1IeBnHi+u3sp8be4ELyIHmFSO5p8DUdJUVt4=
google.golang.org/genproto v0.0.0-20200814021100-8c09557e8a18/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200815001618-f69a88009b70 h1:wboULUXGF3c5qdUnKp+6gLAccE6PRpa/czkYvQ4UXv8=
google.golang.org/genproto v0.0.0-20200815001618-f69a88009b70/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -553,6 +594,8 @@ google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0 h1:M5a8xTlYTxwMn5ZFkwhRabsygDY5G8TYLyQDBxJNAxE=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -574,6 +617,10 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.58.0 h1:VdDvTzv/005R8vEFyQ56bpEnOKTNPbpJhL0VCohxlQw=
gopkg.in/ini.v1 v1.58.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.60.1 h1:P5y5shSkb0CFe44qEeMBgn8JLow09MP17jlJHanke5g=
gopkg.in/ini.v1 v1.60.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -287,8 +287,11 @@ func FetchBlock(conn *grpc.ClientConn, slot uint64) (*ethpb.SignedBeaconBlock, e
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
defer cancel()
req := &ethpb.ListBlocksRequest{
QueryFilter: &ethpb.ListBlocksRequest_Slot{Slot: slot},
req := &ethpb.ListBlocksRequest{}
if slot == 0 {
req.QueryFilter = &ethpb.ListBlocksRequest_Genesis{Genesis: true}
} else {
req.QueryFilter = &ethpb.ListBlocksRequest_Slot{Slot: slot}
}
resp, err := beaconClient.ListBlocks(ctx, req)
if err != nil {

282
util/depositinfo.go Normal file
View File

@@ -0,0 +1,282 @@
// 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 util
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"strings"
"github.com/pkg/errors"
)
// DepositInfo is a generic deposit structure.
type DepositInfo struct {
Name string
Account string
PublicKey []byte
WithdrawalCredentials []byte
Signature []byte
DepositDataRoot []byte
DepositMessageRoot []byte
ForkVersion []byte
Amount uint64
Version uint64
}
// depositInfoV1 is an ethdo V1 deposit structure.
type depositInfoV1 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"`
}
// depositInfoV3 is an ethdo V3 deposit structure.
type depositInfoV3 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"`
DepositMessageRoot string `json:"deposit_message_root"`
ForkVersion string `json:"fork_version"`
Amount uint64 `json:"amount"`
Version uint64 `json:"version"`
}
// depositInfoCLI is a deposit structure from the eth2 deposit CLI.
type depositInfoCLI struct {
PublicKey string `json:"pubkey"`
WithdrawalCredentials string `json:"withdrawal_credentials"`
Signature string `json:"signature"`
DepositDataRoot string `json:"deposit_data_root"`
DepositMessageRoot string `json:"deposit_message_root"`
ForkVersion string `json:"fork_version"`
Amount uint64 `json:"amount"`
}
func DepositInfoFromJSON(input []byte) ([]*DepositInfo, error) {
// Work out the type of data that we're dealing with, and decode it appropriately.
depositInfo, err := tryRawTxData(input)
if err != nil {
depositInfo, err = tryV3DepositInfoFromJSON(input)
if err != nil {
depositInfo, err = tryV1DepositInfoFromJSON(input)
if err != nil {
depositInfo, err = tryCLIDepositInfoFromJSON(input)
if err != nil {
// Give up
return nil, errors.New("unknown deposit data format")
}
}
}
}
if len(depositInfo) == 0 {
return nil, errors.New("no deposits supplied")
}
for i := range depositInfo {
if len(depositInfo[i].PublicKey) == 0 {
return nil, fmt.Errorf("no public key for deposit %d", i)
}
if len(depositInfo[i].DepositDataRoot) == 0 {
return nil, fmt.Errorf("no data root for deposit %d", i)
}
if len(depositInfo[i].Signature) == 0 {
return nil, fmt.Errorf("no signature for deposit %d", i)
}
if len(depositInfo[i].WithdrawalCredentials) == 0 {
return nil, fmt.Errorf("no withdrawal credentials for deposit %d", i)
}
}
return depositInfo, nil
}
func tryV3DepositInfoFromJSON(data []byte) ([]*DepositInfo, error) {
var depositData []*depositInfoV3
err := json.Unmarshal(data, &depositData)
if err != nil {
return nil, err
}
depositInfos := make([]*DepositInfo, len(depositData))
for i, deposit := range depositData {
if deposit.Version != 3 {
return nil, errors.New("incorrect V3 deposit version")
}
publicKey, err := hex.DecodeString(strings.TrimPrefix(deposit.PublicKey, "0x"))
if err != nil {
return nil, errors.New("public key invalid")
}
withdrawalCredentials, err := hex.DecodeString(strings.TrimPrefix(deposit.WithdrawalCredentials, "0x"))
if err != nil {
return nil, errors.New("withdrawal credentials invalid")
}
signature, err := hex.DecodeString(strings.TrimPrefix(deposit.Signature, "0x"))
if err != nil {
return nil, errors.New("signature invalid")
}
depositDataRoot, err := hex.DecodeString(strings.TrimPrefix(deposit.DepositDataRoot, "0x"))
if err != nil {
return nil, errors.New("deposit data root invalid")
}
depositMessageRoot, err := hex.DecodeString(strings.TrimPrefix(deposit.DepositMessageRoot, "0x"))
if err != nil {
return nil, errors.New("deposit message root invalid")
}
forkVersion, err := hex.DecodeString(strings.TrimPrefix(deposit.ForkVersion, "0x"))
if err != nil {
return nil, errors.New("fork version invalid")
}
depositInfos[i] = &DepositInfo{
Name: deposit.Name,
Account: deposit.Account,
PublicKey: publicKey,
WithdrawalCredentials: withdrawalCredentials,
Signature: signature,
DepositDataRoot: depositDataRoot,
DepositMessageRoot: depositMessageRoot,
ForkVersion: forkVersion,
Amount: deposit.Amount,
Version: 3,
}
}
return depositInfos, nil
}
func tryCLIDepositInfoFromJSON(data []byte) ([]*DepositInfo, error) {
var depositData []*depositInfoCLI
err := json.Unmarshal(data, &depositData)
if err != nil {
return nil, err
}
depositInfos := make([]*DepositInfo, len(depositData))
for i, deposit := range depositData {
publicKey, err := hex.DecodeString(strings.TrimPrefix(deposit.PublicKey, "0x"))
if err != nil {
return nil, errors.New("public key invalid")
}
withdrawalCredentials, err := hex.DecodeString(strings.TrimPrefix(deposit.WithdrawalCredentials, "0x"))
if err != nil {
return nil, errors.New("withdrawal credentials invalid")
}
signature, err := hex.DecodeString(strings.TrimPrefix(deposit.Signature, "0x"))
if err != nil {
return nil, errors.New("signature invalid")
}
depositDataRoot, err := hex.DecodeString(strings.TrimPrefix(deposit.DepositDataRoot, "0x"))
if err != nil {
return nil, errors.New("deposit data root invalid")
}
depositMessageRoot, err := hex.DecodeString(strings.TrimPrefix(deposit.DepositMessageRoot, "0x"))
if err != nil {
return nil, errors.New("deposit message root invalid")
}
forkVersion, err := hex.DecodeString(strings.TrimPrefix(deposit.ForkVersion, "0x"))
if err != nil {
return nil, errors.New("fork version invalid")
}
depositInfos[i] = &DepositInfo{
PublicKey: publicKey,
WithdrawalCredentials: withdrawalCredentials,
Signature: signature,
DepositDataRoot: depositDataRoot,
DepositMessageRoot: depositMessageRoot,
ForkVersion: forkVersion,
Amount: deposit.Amount,
Version: 3,
}
}
return depositInfos, nil
}
func tryV1DepositInfoFromJSON(data []byte) ([]*DepositInfo, error) {
var depositData []*depositInfoV1
err := json.Unmarshal(data, &depositData)
if err != nil {
return nil, err
}
depositInfos := make([]*DepositInfo, len(depositData))
for i, deposit := range depositData {
if deposit.Version < 1 || deposit.Version > 2 {
return nil, errors.New("incorrect deposit version")
}
publicKey, err := hex.DecodeString(strings.TrimPrefix(deposit.PublicKey, "0x"))
if err != nil {
return nil, errors.New("public key invalid")
}
withdrawalCredentials, err := hex.DecodeString(strings.TrimPrefix(deposit.WithdrawalCredentials, "0x"))
if err != nil {
return nil, errors.New("withdrawal credentials invalid")
}
signature, err := hex.DecodeString(strings.TrimPrefix(deposit.Signature, "0x"))
if err != nil {
return nil, errors.New("signature invalid")
}
depositDataRoot, err := hex.DecodeString(strings.TrimPrefix(deposit.DepositDataRoot, "0x"))
if err != nil {
return nil, errors.New("deposit data root invalid")
}
depositInfos[i] = &DepositInfo{
Name: deposit.Name,
Account: deposit.Account,
PublicKey: publicKey,
WithdrawalCredentials: withdrawalCredentials,
Signature: signature,
DepositDataRoot: depositDataRoot,
Amount: deposit.Value,
Version: 3,
}
}
return depositInfos, nil
}
func tryRawTxData(data []byte) ([]*DepositInfo, error) {
txData, err := hex.DecodeString(strings.TrimPrefix(string(data), "0x"))
if err != nil {
return nil, errors.New("public key invalid")
}
depositInfos := make([]*DepositInfo, 1)
if len(txData) != 420 {
return nil, errors.New("invalid transaction length")
}
if !bytes.Equal(txData[0:4], []byte{0x22, 0x89, 0x51, 0x18}) {
return nil, errors.New("invalid function signature")
}
depositInfos[0] = &DepositInfo{
PublicKey: txData[164:212],
WithdrawalCredentials: txData[260:292],
Signature: txData[324:420],
DepositDataRoot: txData[100:132],
}
return depositInfos, nil
}