diff --git a/pkg/coordinator/tasks/get_pubkeys_from_mnemonic/README.md b/pkg/coordinator/tasks/get_pubkeys_from_mnemonic/README.md new file mode 100644 index 0000000..b4d6d66 --- /dev/null +++ b/pkg/coordinator/tasks/get_pubkeys_from_mnemonic/README.md @@ -0,0 +1,32 @@ +## `get_pubkeys_from_mnemonic` Task + +### Description +The `get_pubkeys_from_mnemonic` task generates public keys from a given mnemonic phrase. This task is essential for setting up and verifying validator identities in scenarios involving multiple validators derived from a single mnemonic, commonly used in Ethereum staking operations. + +### Configuration Parameters + +- **`mnemonic`**: + The mnemonic phrase used to generate the public keys. This should be a BIP-39 compliant seed phrase that is used to derive Ethereum validator keys. + +- **`start_index`**: + The starting index from which to begin deriving public keys. This allows users to specify a segment of the key sequence for generation, rather than starting from the beginning. + +- **`count`**: + The number of public keys to generate from the specified `start_index`. + +### Outputs + +- **`pubkeys`**: + A list of validator public keys derived from the mnemonic. Each key corresponds to a validator index starting from the `start_index` and continuing for `count` keys. This output is crucial for tasks that involve setting up validators, validating identities, or performing any operation that requires public key data. + +### Defaults + +Default settings for the `get_pubkeys_from_mnemonic` task: + +```yaml +- name: get_pubkeys_from_mnemonic + config: + mnemonic: "" + start_index: 0 + count: 1 +``` diff --git a/pkg/coordinator/tasks/get_pubkeys_from_mnemonic/config.go b/pkg/coordinator/tasks/get_pubkeys_from_mnemonic/config.go new file mode 100644 index 0000000..d60615f --- /dev/null +++ b/pkg/coordinator/tasks/get_pubkeys_from_mnemonic/config.go @@ -0,0 +1,23 @@ +package getpubkeysfrommnemonic + +import "fmt" + +type Config struct { + Mnemonic string `yaml:"mnemonic" json:"mnemonic"` + StartIndex int `yaml:"start_index" json:"start_index"` + Count int `yaml:"count" json:"count"` +} + +func DefaultConfig() Config { + return Config{ + Count: 1, + } +} + +func (c *Config) Validate() error { + if c.Mnemonic == "" { + return fmt.Errorf("mnemonic is required") + } + + return nil +} diff --git a/pkg/coordinator/tasks/get_pubkeys_from_mnemonic/task.go b/pkg/coordinator/tasks/get_pubkeys_from_mnemonic/task.go new file mode 100644 index 0000000..cd4506e --- /dev/null +++ b/pkg/coordinator/tasks/get_pubkeys_from_mnemonic/task.go @@ -0,0 +1,103 @@ +package getpubkeysfrommnemonic + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/sirupsen/logrus" + "github.com/tyler-smith/go-bip39" + util "github.com/wealdtech/go-eth2-util" +) + +var ( + TaskName = "get_pubkeys_from_mnemonic" + TaskDescriptor = &types.TaskDescriptor{ + Name: TaskName, + Description: "Get public keys from mnemonic", + Config: DefaultConfig(), + NewTask: NewTask, + } +) + +type Task struct { + ctx *types.TaskContext + options *types.TaskOptions + config Config + logger logrus.FieldLogger +} + +func NewTask(ctx *types.TaskContext, options *types.TaskOptions) (types.Task, error) { + return &Task{ + ctx: ctx, + options: options, + logger: ctx.Logger.GetLogger(), + }, nil +} + +func (t *Task) Config() interface{} { + return t.config +} + +func (t *Task) Timeout() time.Duration { + return t.options.Timeout.Duration +} + +func (t *Task) LoadConfig() error { + config := DefaultConfig() + + // parse static config + if t.options.Config != nil { + if err := t.options.Config.Unmarshal(&config); err != nil { + return fmt.Errorf("error parsing task config for %v: %w", TaskName, err) + } + } + + // load dynamic vars + err := t.ctx.Vars.ConsumeVars(&config, t.options.ConfigVars) + if err != nil { + return err + } + + // validate config + if err := config.Validate(); err != nil { + return err + } + + t.config = config + + return nil +} + +func (t *Task) Execute(_ context.Context) error { + mnemonic := strings.TrimSpace(t.config.Mnemonic) + if !bip39.IsMnemonicValid(mnemonic) { + return errors.New("mnemonic is not valid") + } + + seed := bip39.NewSeed(mnemonic, "") + pubkeys := make([]string, 0, t.config.Count) + + for index := t.config.StartIndex; index < t.config.StartIndex+t.config.Count; index++ { + validatorKeyPath := fmt.Sprintf("m/12381/3600/%d/0/0", index) + + validatorPriv, err := util.PrivateKeyFromSeedAndPath(seed, validatorKeyPath) + if err != nil { + return fmt.Errorf("failed generating validator key %v: %w", validatorKeyPath, err) + } + + validatorPubkey := validatorPriv.PublicKey().Marshal() + pubkey := fmt.Sprintf("0x%x", validatorPubkey) + + t.logger.Infof("Generated pubkey %v: %v", index, pubkey) + + pubkeys = append(pubkeys, pubkey) + } + + t.ctx.Outputs.SetVar("pubkeys", pubkeys) + + return nil +} diff --git a/pkg/coordinator/tasks/tasks.go b/pkg/coordinator/tasks/tasks.go index b2a9890..59af0b7 100644 --- a/pkg/coordinator/tasks/tasks.go +++ b/pkg/coordinator/tasks/tasks.go @@ -23,6 +23,7 @@ import ( generateslashings "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/generate_slashings" generatetransaction "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/generate_transaction" getconsensusspecs "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/get_consensus_specs" + getpubkeysfrommnemonic "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/get_pubkeys_from_mnemonic" getrandommnemonic "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/get_random_mnemonic" getwalletdetails "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/get_wallet_details" runcommand "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/run_command" @@ -56,6 +57,7 @@ var AvailableTaskDescriptors = []*types.TaskDescriptor{ generateexits.TaskDescriptor, generateslashings.TaskDescriptor, generatetransaction.TaskDescriptor, + getpubkeysfrommnemonic.TaskDescriptor, getconsensusspecs.TaskDescriptor, getrandommnemonic.TaskDescriptor, getwalletdetails.TaskDescriptor,