mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 15:37:56 -05:00
Part 1: Implement Accounts-V2 New, Wallet Creation (#6451)
* begin accounts-v2 new * password validation * validator accounts new with eip-2335 keystore * select different wallet type based on enum * clean up code significantly * more robust code structure * check if wallet exists * define read and create wallet methods * fmt * go mod and comment * comment * redundant name * satify gofmt * add instructions with keymanager opts * wrap up create and read wallet functionality * prep for readiness * doc improvements * tests for create and read wallet * update deps * tidy * visibility * gaz * fix up * refactor for proper usage, with wallet and keymanager ifaces * Update validator/flags/flags.go Co-authored-by: Ivan Martinez <ivanthegreatdev@gmail.com> * import * improve structure * wrap up all comments * simplify * lint * Update validator/accounts/v2/cmd.go * viz check * add interface methods as needed * fix build * lint * nishant feedback * simplify structure * add tests for strong password check * all feedback done * ivan feedback * ivan feedback Co-authored-by: Ivan Martinez <ivanthegreatdev@gmail.com> Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
This commit is contained in:
@@ -157,7 +157,7 @@ go_rules_dependencies()
|
||||
|
||||
go_register_toolchains(nogo = "@//:nogo")
|
||||
|
||||
load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies")
|
||||
load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies", "go_repository")
|
||||
|
||||
gazelle_dependencies()
|
||||
|
||||
@@ -354,3 +354,10 @@ load("@com_github_ethereum_go_ethereum//:deps.bzl", "geth_dependencies")
|
||||
geth_dependencies()
|
||||
|
||||
# Do NOT add new go dependencies here! Refer to DEPENDENCIES.md!
|
||||
|
||||
go_repository(
|
||||
name = "com_github_nbutton23_zxcvbn_go",
|
||||
importpath = "github.com/nbutton23/zxcvbn-go",
|
||||
sum = "h1:AREM5mwr4u1ORQBMvzfzBgpsctsbQikCVpvC+tX285E=",
|
||||
version = "v0.0.0-20180912185939-ae427f1e4c1d",
|
||||
)
|
||||
|
||||
20
deps.bzl
20
deps.bzl
@@ -3519,3 +3519,23 @@ def prysm_deps():
|
||||
sum = "h1:Qh4dB5D/WpoUUp3lSod7qgoyEHbDGPUWjIbnqdqqe1k=",
|
||||
version = "v0.0.0-20190515093506-e2840ee46a6b",
|
||||
)
|
||||
go_repository(
|
||||
name = "com_github_juju_ansiterm",
|
||||
importpath = "github.com/juju/ansiterm",
|
||||
sum = "h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=",
|
||||
version = "v0.0.0-20180109212912-720a0952cc2a",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "com_github_manifoldco_promptui",
|
||||
importpath = "github.com/manifoldco/promptui",
|
||||
sum = "h1:3l11YT8tm9MnwGFQ4kETwkzpAwY2Jt9lCrumCUW4+z4=",
|
||||
version = "v0.7.0",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "com_github_dustinkirkland_golang_petname",
|
||||
importpath = "github.com/dustinkirkland/golang-petname",
|
||||
sum = "h1:90Ly+6UfUypEF6vvvW5rQIv9opIL8CbmW9FT20LDQoY=",
|
||||
version = "v0.0.0-20191129215211-8e5a1ed0cff0",
|
||||
)
|
||||
|
||||
2
go.mod
2
go.mod
@@ -65,11 +65,13 @@ require (
|
||||
github.com/libp2p/go-libp2p-tls v0.1.4-0.20200421131144-8a8ad624a291 // indirect
|
||||
github.com/libp2p/go-libp2p-yamux v0.2.8 // indirect
|
||||
github.com/libp2p/go-maddr-filter v0.1.0 // indirect
|
||||
github.com/manifoldco/promptui v0.7.0
|
||||
github.com/minio/highwayhash v1.0.0
|
||||
github.com/minio/sha256-simd v0.1.1
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
||||
github.com/multiformats/go-multiaddr v0.2.2
|
||||
github.com/multiformats/go-multiaddr-net v0.1.5
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d
|
||||
github.com/olekukonko/tablewriter v0.0.4 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/paulbellamy/ratecounter v0.2.0
|
||||
|
||||
14
go.sum
14
go.sum
@@ -113,6 +113,7 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||
github.com/c-bata/go-prompt v0.2.2 h1:uyKRz6Z6DUyj49QVijyM339UJV9yhbr70gESwbNU3e0=
|
||||
github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34=
|
||||
github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
@@ -125,8 +126,11 @@ github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod h1:1MxXX1Ux4x6mqPmjkUgTP1CdXIBXKX7T+Jk9Gxrmx+U=
|
||||
@@ -456,6 +460,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
|
||||
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
|
||||
github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
@@ -682,12 +688,16 @@ github.com/libp2p/go-yamux v1.3.7 h1:v40A1eSPJDIZwz2AvrV3cxpTZEGDP11QJbukmEhYyQI
|
||||
github.com/libp2p/go-yamux v1.3.7/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE=
|
||||
github.com/lucas-clemente/quic-go v0.15.7 h1:Pu7To5/G9JoP1mwlrcIvfV8ByPBlCzif3MCl8+1W83I=
|
||||
github.com/lucas-clemente/quic-go v0.15.7/go.mod h1:Myi1OyS0FOjL3not4BxT7KN29bRkcMUV5JVVFLKtDp8=
|
||||
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/magiconair/properties v1.7.4-0.20170902060319-8d7837e64d3c/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/manifoldco/promptui v0.7.0 h1:3l11YT8tm9MnwGFQ4kETwkzpAwY2Jt9lCrumCUW4+z4=
|
||||
github.com/manifoldco/promptui v0.7.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ=
|
||||
github.com/marten-seemann/qpack v0.1.0/go.mod h1:LFt1NU/Ptjip0C2CPkhimBz5CGE3WGDAUWqna+CNTrI=
|
||||
github.com/marten-seemann/qtls v0.9.1 h1:O0YKQxNVPaiFgMng0suWEOY2Sb4LT2sRn9Qimq3Z1IQ=
|
||||
github.com/marten-seemann/qtls v0.9.1/go.mod h1:T1MmAdDPyISzxlK6kjRr0pcZFBVd1OZbBb/j3cvzHhk=
|
||||
@@ -798,6 +808,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0=
|
||||
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1ORQBMvzfzBgpsctsbQikCVpvC+tX285E=
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
|
||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
@@ -852,6 +864,7 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5 h1:tFwafIEMf0B7NlcxV/zJ6leBIa81D3hgGSgsE5hCkOQ=
|
||||
github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
@@ -1230,6 +1243,7 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
||||
@@ -22,6 +22,7 @@ go_library(
|
||||
"//shared/params:go_default_library",
|
||||
"//shared/version:go_default_library",
|
||||
"//validator/accounts/v1:go_default_library",
|
||||
"//validator/accounts/v2:go_default_library",
|
||||
"//validator/client/streaming:go_default_library",
|
||||
"//validator/flags:go_default_library",
|
||||
"//validator/node:go_default_library",
|
||||
@@ -63,7 +64,7 @@ go_image(
|
||||
"//shared/params:go_default_library",
|
||||
"//shared/version:go_default_library",
|
||||
"//validator/accounts/v1:go_default_library",
|
||||
"//validator/client/polling:go_default_library",
|
||||
"//validator/accounts/v2:go_default_library",
|
||||
"//validator/client/streaming:go_default_library",
|
||||
"//validator/flags:go_default_library",
|
||||
"//validator/node:go_default_library",
|
||||
|
||||
43
validator/accounts/v2/BUILD.bazel
Normal file
43
validator/accounts/v2/BUILD.bazel
Normal file
@@ -0,0 +1,43 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_test")
|
||||
load("@prysm//tools/go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"cmd.go",
|
||||
"doc.go",
|
||||
"new.go",
|
||||
"wallet.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/validator/accounts/v2",
|
||||
visibility = [
|
||||
"//validator:__pkg__",
|
||||
"//validator:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//shared/featureconfig:go_default_library",
|
||||
"//validator/flags:go_default_library",
|
||||
"//validator/keymanager/v2:go_default_library",
|
||||
"//validator/keymanager/v2/direct:go_default_library",
|
||||
"@com_github_manifoldco_promptui//:go_default_library",
|
||||
"@com_github_nbutton23_zxcvbn_go//:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"new_test.go",
|
||||
"wallet_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//shared/testutil:go_default_library",
|
||||
"//validator/keymanager/v2:go_default_library",
|
||||
"//validator/keymanager/v2/direct:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
],
|
||||
)
|
||||
28
validator/accounts/v2/cmd.go
Normal file
28
validator/accounts/v2/cmd.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"github.com/prysmaticlabs/prysm/shared/featureconfig"
|
||||
"github.com/prysmaticlabs/prysm/validator/flags"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// Commands for accounts-v2 for Prysm validators.
|
||||
var Commands = &cli.Command{
|
||||
Name: "accounts-v2",
|
||||
Category: "accounts-v2",
|
||||
Usage: "defines commands for interacting with eth2 validator accounts (work in progress)",
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "new",
|
||||
Description: `creates a new validator account for eth2. If no account exists at the wallet path, creates a new wallet for a user based on
|
||||
specified input, capable of creating a direct, derived, or remote wallet.
|
||||
this command outputs a deposit data string which is required to become a validator in eth2.`,
|
||||
Flags: append(featureconfig.ActiveFlags(featureconfig.ValidatorFlags),
|
||||
[]cli.Flag{
|
||||
flags.WalletDirFlag,
|
||||
flags.WalletPasswordsDirFlag,
|
||||
}...),
|
||||
Action: NewAccount,
|
||||
},
|
||||
},
|
||||
}
|
||||
4
validator/accounts/v2/doc.go
Normal file
4
validator/accounts/v2/doc.go
Normal file
@@ -0,0 +1,4 @@
|
||||
// Package v2 defines a new model for accounts management in Prysm, using best
|
||||
// practices for user security, UX, and extensibility via different wallet types
|
||||
// including derived, HD wallets and remote-signing capable configurations.
|
||||
package v2
|
||||
304
validator/accounts/v2/new.go
Normal file
304
validator/accounts/v2/new.go
Normal file
@@ -0,0 +1,304 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"unicode"
|
||||
|
||||
"github.com/manifoldco/promptui"
|
||||
strongPasswords "github.com/nbutton23/zxcvbn-go"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/validator/flags"
|
||||
v2keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
|
||||
"github.com/prysmaticlabs/prysm/validator/keymanager/v2/direct"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var log = logrus.WithField("prefix", "accounts-v2")
|
||||
|
||||
const (
|
||||
minPasswordLength = 8
|
||||
// Min password score of 3 out of 5 based on the https://github.com/nbutton23/zxcvbn-go
|
||||
// library for strong-entropy password computation.
|
||||
minPasswordScore = 3
|
||||
)
|
||||
|
||||
var keymanagerKindSelections = map[v2keymanager.Kind]string{
|
||||
v2keymanager.Direct: "Direct, On-Disk Accounts (Recommended)",
|
||||
v2keymanager.Derived: "Derived Accounts (Advanced)",
|
||||
v2keymanager.Remote: "Remote Accounts (Advanced)",
|
||||
}
|
||||
|
||||
// NewAccount creates a new validator account from user input. If a user
|
||||
// does not have an initialized wallet at the specified wallet path, this
|
||||
// method will create a new wallet and ask user for input for their new wallet's
|
||||
// available options.
|
||||
func NewAccount(cliCtx *cli.Context) error {
|
||||
// Read a wallet's directory from user input.
|
||||
walletDir, err := inputWalletDir(cliCtx)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not parse wallet directory: %v", err)
|
||||
}
|
||||
|
||||
// Read the directory for password storage from user input.
|
||||
passwordsDirPath := inputPasswordsDirectory(cliCtx)
|
||||
|
||||
ctx := context.Background()
|
||||
// Check if the user has a wallet at the specified path.
|
||||
// If a user does not have a wallet, we instantiate one
|
||||
// based on specified options.
|
||||
var wallet *Wallet
|
||||
var isNewWallet bool
|
||||
ok, err := hasWalletDir(walletDir)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not check if wallet exists at %s: %v", walletDir, err)
|
||||
}
|
||||
if ok {
|
||||
// Read the wallet from the specified path.
|
||||
wallet, err = OpenWallet(ctx, &WalletConfig{
|
||||
PasswordsDir: passwordsDirPath,
|
||||
WalletDir: walletDir,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Could not read wallet at specified path %s: %v", walletDir, err)
|
||||
}
|
||||
} else {
|
||||
// Determine the desired keymanager kind for the wallet from user input.
|
||||
keymanagerKind, err := inputKeymanagerKind(cliCtx)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not select keymanager kind: %v", err)
|
||||
}
|
||||
|
||||
walletConfig := &WalletConfig{
|
||||
PasswordsDir: passwordsDirPath,
|
||||
WalletDir: walletDir,
|
||||
KeymanagerKind: keymanagerKind,
|
||||
}
|
||||
wallet, err = CreateWallet(ctx, walletConfig)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not create wallet at specified path %s: %v", walletDir, err)
|
||||
}
|
||||
isNewWallet = true
|
||||
}
|
||||
|
||||
// We initialize a new keymanager depending on the user's selected keymanager kind.
|
||||
var keymanager v2keymanager.IKeymanager
|
||||
if isNewWallet {
|
||||
keymanager, err = initializeNewKeymanager(ctx, wallet)
|
||||
} else {
|
||||
keymanager, err = initializeExistingKeymanager(ctx, wallet)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalf("Could not initialize keymanager: %v", err)
|
||||
}
|
||||
|
||||
// Read the new account's password from user input.
|
||||
password, err := inputAccountPassword(cliCtx)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not read password: %v", err)
|
||||
}
|
||||
|
||||
// Create a new validator account using the specified keymanager.
|
||||
// TODO(#6220): Implement.
|
||||
if err := keymanager.CreateAccount(ctx, password); err != nil {
|
||||
log.Fatalf("Could not create account in wallet: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Initializes a keymanager. If a config file exists in the wallet, it
|
||||
// reads the config file and initializes the keymanager that way. Otherwise,
|
||||
// writes a new configuration file to the wallet and returns the initialized
|
||||
// keymanager for use.
|
||||
func initializeNewKeymanager(ctx context.Context, wallet *Wallet) (v2keymanager.IKeymanager, error) {
|
||||
var keymanager v2keymanager.IKeymanager
|
||||
var err error
|
||||
switch wallet.KeymanagerKind() {
|
||||
case v2keymanager.Direct:
|
||||
keymanager = direct.NewKeymanager(ctx, wallet, direct.DefaultConfig())
|
||||
case v2keymanager.Derived:
|
||||
return nil, errors.New("derived keymanager is unimplemented, work in progress")
|
||||
case v2keymanager.Remote:
|
||||
return nil, errors.New("remote keymanager is unimplemented, work in progress")
|
||||
default:
|
||||
log.Fatal("Keymanager type must be specified")
|
||||
}
|
||||
keymanagerConfig, err := keymanager.MarshalConfigFile(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not marshal keymanager config file: %v", err)
|
||||
}
|
||||
if err := wallet.WriteKeymanagerConfigToDisk(ctx, keymanagerConfig); err != nil {
|
||||
log.Fatalf("Could not write keymanager config file to disk: %v", err)
|
||||
}
|
||||
return keymanager, nil
|
||||
}
|
||||
|
||||
func initializeExistingKeymanager(ctx context.Context, wallet *Wallet) (v2keymanager.IKeymanager, error) {
|
||||
var keymanager v2keymanager.IKeymanager
|
||||
var err error
|
||||
switch wallet.KeymanagerKind() {
|
||||
case v2keymanager.Direct:
|
||||
keymanager, err = direct.NewKeymanagerFromConfigFile(ctx, wallet)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not initialize direct keymanager from config")
|
||||
}
|
||||
case v2keymanager.Derived:
|
||||
return nil, errors.New("derived keymanager is unimplemented, work in progress")
|
||||
case v2keymanager.Remote:
|
||||
return nil, errors.New("remote keymanager is unimplemented, work in progress")
|
||||
default:
|
||||
return nil, errors.New("keymanager kind must be specified")
|
||||
}
|
||||
return keymanager, nil
|
||||
}
|
||||
|
||||
// Check if a user has an existing wallet at the specified path.
|
||||
func hasWalletDir(walletPath string) (bool, error) {
|
||||
_, err := os.Stat(walletPath)
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
|
||||
func inputWalletDir(cliCtx *cli.Context) (string, error) {
|
||||
walletDir := cliCtx.String(flags.WalletDirFlag.Name)
|
||||
if walletDir == flags.DefaultValidatorDir() {
|
||||
walletDir = path.Join(walletDir, walletDefaultDirName)
|
||||
}
|
||||
prompt := promptui.Prompt{
|
||||
Label: "Enter a wallet directory",
|
||||
Validate: validateDirectoryPath,
|
||||
Default: walletDir,
|
||||
}
|
||||
walletPath, err := prompt.Run()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not determine wallet directory: %v", formatPromptError(err))
|
||||
}
|
||||
return walletPath, nil
|
||||
}
|
||||
|
||||
func inputKeymanagerKind(_ *cli.Context) (v2keymanager.Kind, error) {
|
||||
promptSelect := promptui.Select{
|
||||
Label: "Select a type of wallet",
|
||||
Items: []string{
|
||||
keymanagerKindSelections[v2keymanager.Direct],
|
||||
keymanagerKindSelections[v2keymanager.Derived],
|
||||
keymanagerKindSelections[v2keymanager.Remote],
|
||||
},
|
||||
}
|
||||
selection, _, err := promptSelect.Run()
|
||||
if err != nil {
|
||||
return v2keymanager.Direct, fmt.Errorf("could not select wallet type: %v", formatPromptError(err))
|
||||
}
|
||||
return v2keymanager.Kind(selection), nil
|
||||
}
|
||||
|
||||
func inputAccountPassword(_ *cli.Context) (string, error) {
|
||||
var hasValidPassword bool
|
||||
var walletPassword string
|
||||
for !hasValidPassword {
|
||||
prompt := promptui.Prompt{
|
||||
Label: "New account password",
|
||||
Validate: validatePasswordInput,
|
||||
Mask: '*',
|
||||
}
|
||||
|
||||
walletPassword, err := prompt.Run()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not read wallet password: %v", formatPromptError(err))
|
||||
}
|
||||
|
||||
prompt = promptui.Prompt{
|
||||
Label: "Confirm password",
|
||||
Mask: '*',
|
||||
}
|
||||
confirmPassword, err := prompt.Run()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not read password confirmation: %v", formatPromptError(err))
|
||||
}
|
||||
if walletPassword != confirmPassword {
|
||||
log.Error("Passwords do not match")
|
||||
continue
|
||||
}
|
||||
hasValidPassword = true
|
||||
}
|
||||
return walletPassword, nil
|
||||
}
|
||||
|
||||
func inputPasswordsDirectory(cliCtx *cli.Context) string {
|
||||
passwordsDir := cliCtx.String(flags.WalletPasswordsDirFlag.Name)
|
||||
if passwordsDir == flags.DefaultValidatorDir() {
|
||||
passwordsDir = path.Join(passwordsDir, walletDefaultDirName, passwordsDefaultDirName)
|
||||
}
|
||||
prompt := promptui.Prompt{
|
||||
Label: "Passwords directory",
|
||||
Validate: validateDirectoryPath,
|
||||
Default: passwordsDir,
|
||||
}
|
||||
passwordsPath, err := prompt.Run()
|
||||
if err != nil {
|
||||
log.Fatalf("Could not determine passwords directory: %v", formatPromptError(err))
|
||||
}
|
||||
return passwordsPath
|
||||
}
|
||||
|
||||
// Validate a strong password input for new accounts,
|
||||
// including a min length, at least 1 number and at least
|
||||
// 1 special character.
|
||||
func validatePasswordInput(input string) error {
|
||||
var (
|
||||
hasMinLen = false
|
||||
hasLetter = false
|
||||
hasNumber = false
|
||||
hasSpecial = false
|
||||
)
|
||||
if len(input) >= minPasswordLength {
|
||||
hasMinLen = true
|
||||
}
|
||||
for _, char := range input {
|
||||
switch {
|
||||
case unicode.IsLetter(char):
|
||||
hasLetter = true
|
||||
case unicode.IsNumber(char):
|
||||
hasNumber = true
|
||||
case unicode.IsPunct(char) || unicode.IsSymbol(char):
|
||||
hasSpecial = true
|
||||
}
|
||||
}
|
||||
if !(hasMinLen && hasLetter && hasNumber && hasSpecial) {
|
||||
return errors.New(
|
||||
"password must have more than 8 characters, at least 1 special character, and 1 number",
|
||||
)
|
||||
}
|
||||
strength := strongPasswords.PasswordStrength(input, nil)
|
||||
if strength.Score < minPasswordScore {
|
||||
return errors.New(
|
||||
"password is too easy to guess, try a stronger password",
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateDirectoryPath(input string) error {
|
||||
if len(input) == 0 {
|
||||
return errors.New("directory path must not be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatPromptError(err error) error {
|
||||
switch err {
|
||||
case promptui.ErrAbort:
|
||||
return errors.New("wallet creation aborted, closing")
|
||||
case promptui.ErrInterrupt:
|
||||
return errors.New("keyboard interrupt, closing")
|
||||
case promptui.ErrEOF:
|
||||
return errors.New("no input received, closing")
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
44
validator/accounts/v2/new_test.go
Normal file
44
validator/accounts/v2/new_test.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package v2
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test_validatePasswordInput(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "no numbers nor special characters",
|
||||
input: "abcdefghijklmnopqrs",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "number and letters but no special characters",
|
||||
input: "abcdefghijklmnopqrs2020",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "numbers, letters, special characters, but too short",
|
||||
input: "abc2$",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "proper length and strong password",
|
||||
input: "%Str0ngpassword32kjAjsd22020$%",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "password format correct but weak entropy score",
|
||||
input: "aaaaaaa1$",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := validatePasswordInput(tt.input); (err != nil) != tt.wantErr {
|
||||
t.Errorf("validatePasswordInput() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
140
validator/accounts/v2/wallet.go
Normal file
140
validator/accounts/v2/wallet.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
v2keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
keymanagerConfigFileName = "keymanageropts.json"
|
||||
walletDefaultDirName = ".prysm-wallet-v2"
|
||||
passwordsDefaultDirName = ".passwords"
|
||||
)
|
||||
|
||||
// WalletConfig for a wallet struct, containing important information
|
||||
// such as the passwords directory, the wallet's directory, and keymanager.
|
||||
type WalletConfig struct {
|
||||
PasswordsDir string
|
||||
WalletDir string
|
||||
KeymanagerKind v2keymanager.Kind
|
||||
}
|
||||
|
||||
// Wallet is a primitive in Prysm's v2 account management which
|
||||
// has the capability of creating new accounts, reading existing accounts,
|
||||
// and providing secure access to eth2 secrets depending on an
|
||||
// associated keymanager (either direct, derived, or remote signing enabled).
|
||||
type Wallet struct {
|
||||
accountsPath string
|
||||
passwordsDir string
|
||||
keymanagerKind v2keymanager.Kind
|
||||
}
|
||||
|
||||
// CreateWallet given a set of configuration options, will leverage
|
||||
// a keymanager to create and write a new wallet to disk for a Prysm validator.
|
||||
func CreateWallet(ctx context.Context, cfg *WalletConfig) (*Wallet, error) {
|
||||
if cfg.WalletDir == "" || cfg.PasswordsDir == "" {
|
||||
return nil, errors.New("wallet dir and passwords dir cannot be nil")
|
||||
}
|
||||
accountsPath := path.Join(cfg.WalletDir, cfg.KeymanagerKind.String())
|
||||
if err := os.MkdirAll(accountsPath, os.ModePerm); err != nil {
|
||||
return nil, errors.Wrap(err, "could not create wallet directory")
|
||||
}
|
||||
if err := os.MkdirAll(cfg.PasswordsDir, os.ModePerm); err != nil {
|
||||
return nil, errors.Wrap(err, "could not create passwords directory")
|
||||
}
|
||||
w := &Wallet{
|
||||
accountsPath: accountsPath,
|
||||
passwordsDir: cfg.PasswordsDir,
|
||||
keymanagerKind: cfg.KeymanagerKind,
|
||||
}
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// OpenWallet instantiates a wallet from a specified path.
|
||||
func OpenWallet(ctx context.Context, cfg *WalletConfig) (*Wallet, error) {
|
||||
walletPath := path.Join(cfg.WalletDir, cfg.KeymanagerKind.String())
|
||||
return &Wallet{
|
||||
accountsPath: walletPath,
|
||||
passwordsDir: cfg.PasswordsDir,
|
||||
keymanagerKind: cfg.KeymanagerKind,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ReadKeymanagerConfigFromDisk opens a keymanager config file
|
||||
// for reading if it exists at the wallet path.
|
||||
func (w *Wallet) ReadKeymanagerConfigFromDisk(ctx context.Context) (io.ReadCloser, error) {
|
||||
if !fileExists(path.Join(w.accountsPath, keymanagerConfigFileName)) {
|
||||
return nil, fmt.Errorf("no keymanager config file found at path: %s", w.accountsPath)
|
||||
}
|
||||
configFilePath := path.Join(w.accountsPath, keymanagerConfigFileName)
|
||||
return os.Open(configFilePath)
|
||||
}
|
||||
|
||||
// KeymanagerKind used by the wallet.
|
||||
func (w *Wallet) KeymanagerKind() v2keymanager.Kind {
|
||||
return w.keymanagerKind
|
||||
}
|
||||
|
||||
// AccountsPath for the wallet.
|
||||
func (w *Wallet) AccountsPath() string {
|
||||
return w.accountsPath
|
||||
}
|
||||
|
||||
// AccountPasswordsPath for the wallet's accounts.
|
||||
func (w *Wallet) AccountPasswordsPath() string {
|
||||
return w.passwordsDir
|
||||
}
|
||||
|
||||
// WriteAccountToDisk writes an encoded account by its filename
|
||||
// within the wallet's directory.
|
||||
func (w *Wallet) WriteAccountToDisk(ctx context.Context, filename string, encoded []byte) error {
|
||||
return errors.New("unimplemented")
|
||||
}
|
||||
|
||||
// WriteKeymanagerConfigToDisk takes an encoded keymanager config file
|
||||
// and writes it to the wallet path.
|
||||
func (w *Wallet) WriteKeymanagerConfigToDisk(ctx context.Context, encoded []byte) error {
|
||||
configFilePath := path.Join(w.accountsPath, keymanagerConfigFileName)
|
||||
if fileExists(configFilePath) {
|
||||
return nil
|
||||
}
|
||||
// Open the keymanager config file for writing.
|
||||
f, err := os.Create(configFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
log.Fatalf("Could not close keymanager opts file: %v", err)
|
||||
}
|
||||
}()
|
||||
n, err := f.Write(encoded)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != len(encoded) {
|
||||
return fmt.Errorf(
|
||||
"expected to write %d bytes to disk, but wrote %d",
|
||||
len(encoded),
|
||||
n,
|
||||
)
|
||||
}
|
||||
log.WithField("configFile", configFilePath).Debug("Wrote keymanager config file to disk")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns true if a file is not a directory and exists
|
||||
// at the specified path.
|
||||
func fileExists(filename string) bool {
|
||||
info, err := os.Stat(filename)
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return !info.IsDir()
|
||||
}
|
||||
106
validator/accounts/v2/wallet_test.go
Normal file
106
validator/accounts/v2/wallet_test.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/shared/testutil"
|
||||
v2keymanager "github.com/prysmaticlabs/prysm/validator/keymanager/v2"
|
||||
"github.com/prysmaticlabs/prysm/validator/keymanager/v2/direct"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
logrus.SetOutput(ioutil.Discard)
|
||||
}
|
||||
|
||||
var _ = direct.Wallet(&Wallet{})
|
||||
|
||||
type mockKeymanager struct {
|
||||
configFileContents []byte
|
||||
}
|
||||
|
||||
func (m *mockKeymanager) CreateAccount(ctx context.Context, password string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockKeymanager) MarshalConfigFile(ctx context.Context) ([]byte, error) {
|
||||
return m.configFileContents, nil
|
||||
}
|
||||
|
||||
func setupWalletDir(t testing.TB) (string, string) {
|
||||
randPath, err := rand.Int(rand.Reader, big.NewInt(1000000))
|
||||
if err != nil {
|
||||
t.Fatalf("Could not generate random file path: %v", err)
|
||||
}
|
||||
walletDir := path.Join(testutil.TempDir(), fmt.Sprintf("/%d", randPath))
|
||||
if err := os.RemoveAll(walletDir); err != nil {
|
||||
t.Fatalf("Failed to remove directory: %v", err)
|
||||
}
|
||||
passwordsDir := path.Join(testutil.TempDir(), fmt.Sprintf("/%d", randPath))
|
||||
if err := os.RemoveAll(passwordsDir); err != nil {
|
||||
t.Fatalf("Failed to remove directory: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if err := os.RemoveAll(walletDir); err != nil {
|
||||
t.Fatalf("Failed to remove directory: %v", err)
|
||||
}
|
||||
if err := os.RemoveAll(passwordsDir); err != nil {
|
||||
t.Fatalf("Failed to remove directory: %v", err)
|
||||
}
|
||||
})
|
||||
return walletDir, passwordsDir
|
||||
}
|
||||
|
||||
func TestCreateAndReadWallet(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
if _, err := CreateWallet(ctx, &WalletConfig{
|
||||
PasswordsDir: "",
|
||||
WalletDir: "",
|
||||
}); err == nil {
|
||||
t.Error("Expected error when passing in empty directories, received nil")
|
||||
}
|
||||
walletDir, passwordsDir := setupWalletDir(t)
|
||||
keymanagerKind := v2keymanager.Direct
|
||||
wallet, err := CreateWallet(ctx, &WalletConfig{
|
||||
PasswordsDir: passwordsDir,
|
||||
WalletDir: walletDir,
|
||||
KeymanagerKind: keymanagerKind,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
keymanager := &mockKeymanager{
|
||||
configFileContents: []byte("hello-world"),
|
||||
}
|
||||
keymanagerConfig, err := keymanager.MarshalConfigFile(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not marshal keymanager config file: %v", err)
|
||||
}
|
||||
if err := wallet.WriteKeymanagerConfigToDisk(ctx, keymanagerConfig); err != nil {
|
||||
t.Fatalf("Could not write keymanager config file to disk: %v", err)
|
||||
}
|
||||
|
||||
walletPath := path.Join(walletDir, keymanagerKind.String())
|
||||
configFilePath := path.Join(walletPath, keymanagerConfigFileName)
|
||||
if !fileExists(configFilePath) {
|
||||
t.Fatalf("Expected config file to have been created at path: %s", configFilePath)
|
||||
}
|
||||
|
||||
// We should be able to now read the wallet as well.
|
||||
if _, err := CreateWallet(ctx, &WalletConfig{
|
||||
PasswordsDir: passwordsDir,
|
||||
WalletDir: walletDir,
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,11 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
@@ -108,4 +113,45 @@ var (
|
||||
Usage: "Filepath to a JSON file of unencrypted validator keys for easier launching of the validator client",
|
||||
Value: "",
|
||||
}
|
||||
// WalletDirFlag defines the path to a wallet directory for Prysm accounts-v2.
|
||||
WalletDirFlag = &cli.StringFlag{
|
||||
Name: "wallet-dir",
|
||||
Usage: "Path to a wallet directory on-disk for Prysm validator accounts",
|
||||
Value: DefaultValidatorDir(),
|
||||
}
|
||||
// WalletPasswordsDirFlag defines the path for a passwords directory for
|
||||
// Prysm accounts-v2.
|
||||
WalletPasswordsDirFlag = &cli.StringFlag{
|
||||
Name: "passwords-dir",
|
||||
Usage: "Path to a directory on-disk where wallet passwords are stored",
|
||||
Value: DefaultValidatorDir(),
|
||||
}
|
||||
)
|
||||
|
||||
// DefaultValidatorDir returns OS-specific default validator directory.
|
||||
func DefaultValidatorDir() string {
|
||||
// Try to place the data folder in the user's home dir
|
||||
home := homeDir()
|
||||
if home != "" {
|
||||
if runtime.GOOS == "darwin" {
|
||||
return filepath.Join(home, "Library", "Eth2Validators")
|
||||
} else if runtime.GOOS == "windows" {
|
||||
return filepath.Join(home, "AppData", "Roaming", "Eth2Validators")
|
||||
} else {
|
||||
return filepath.Join(home, ".eth2validators")
|
||||
}
|
||||
}
|
||||
// As we cannot guess a stable location, return empty and handle later
|
||||
return ""
|
||||
}
|
||||
|
||||
// homeDir returns home directory path.
|
||||
func homeDir() string {
|
||||
if home := os.Getenv("HOME"); home != "" {
|
||||
return home
|
||||
}
|
||||
if usr, err := user.Current(); err == nil {
|
||||
return usr.HomeDir
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
20
validator/keymanager/v2/BUILD.bazel
Normal file
20
validator/keymanager/v2/BUILD.bazel
Normal file
@@ -0,0 +1,20 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_test")
|
||||
load("@prysm//tools/go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["types.go"],
|
||||
importpath = "github.com/prysmaticlabs/prysm/validator/keymanager/v2",
|
||||
visibility = [
|
||||
"//validator:__pkg__",
|
||||
"//validator:__subpackages__",
|
||||
],
|
||||
deps = ["//shared/bls:go_default_library"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["types_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = ["//validator/keymanager/v2/direct:go_default_library"],
|
||||
)
|
||||
15
validator/keymanager/v2/direct/BUILD.bazel
Normal file
15
validator/keymanager/v2/direct/BUILD.bazel
Normal file
@@ -0,0 +1,15 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["direct.go"],
|
||||
importpath = "github.com/prysmaticlabs/prysm/validator/keymanager/v2/direct",
|
||||
visibility = [
|
||||
"//validator:__pkg__",
|
||||
"//validator:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//shared/bls:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
],
|
||||
)
|
||||
74
validator/keymanager/v2/direct/direct.go
Normal file
74
validator/keymanager/v2/direct/direct.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package direct
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/shared/bls"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var log = logrus.WithField("prefix", "keymanager-v2")
|
||||
|
||||
// Wallet defines a struct which has capabilities and knowledge of how
|
||||
// to read and write important accounts-related files to the filesystem.
|
||||
// Useful for keymanager to have persistent capabilities for accounts on-disk.
|
||||
type Wallet interface {
|
||||
AccountsPath() string
|
||||
AccountPasswordsPath() string
|
||||
WriteAccountToDisk(ctx context.Context, filename string, encoded []byte) error
|
||||
WriteKeymanagerConfigToDisk(ctx context.Context, encoded []byte) error
|
||||
ReadKeymanagerConfigFromDisk(ctx context.Context) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
// Config for a direct keymanager.
|
||||
type Config struct{}
|
||||
|
||||
// Keymanager implementation for direct keystores.
|
||||
type Keymanager struct {
|
||||
wallet Wallet
|
||||
}
|
||||
|
||||
// DefaultConfig for a direct keymanager implementation.
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{}
|
||||
}
|
||||
|
||||
// NewKeymanager instantiates a new direct keymanager from configuration options.
|
||||
func NewKeymanager(ctx context.Context, wallet Wallet, cfg *Config) *Keymanager {
|
||||
return &Keymanager{
|
||||
wallet: wallet,
|
||||
}
|
||||
}
|
||||
|
||||
// NewKeymanagerFromConfigFile instantiates a direct keymanager instance
|
||||
// from a configuration file accesed via a wallet.
|
||||
// TODO(#6220): Implement.
|
||||
func NewKeymanagerFromConfigFile(ctx context.Context, wallet Wallet) (*Keymanager, error) {
|
||||
return &Keymanager{
|
||||
wallet: wallet,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateAccount for a direct keymanager implementation.
|
||||
// TODO(#6220): Implement.
|
||||
func (dr *Keymanager) CreateAccount(ctx context.Context, password string) error {
|
||||
return errors.New("unimplemented")
|
||||
}
|
||||
|
||||
// MarshalConfigFile returns a marshaled configuration file for a direct keymanager.
|
||||
// TODO(#6220): Implement.
|
||||
func (dr *Keymanager) MarshalConfigFile(ctx context.Context) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// FetchValidatingPublicKeys fetches the list of public keys from the direct account keystores.
|
||||
func (dr *Keymanager) FetchValidatingPublicKeys() ([][48]byte, error) {
|
||||
return nil, errors.New("unimplemented")
|
||||
}
|
||||
|
||||
// Sign signs a message using a validator key.
|
||||
func (dr *Keymanager) Sign(context.Context, interface{}) (bls.Signature, error) {
|
||||
return nil, errors.New("unimplemented")
|
||||
}
|
||||
47
validator/keymanager/v2/types.go
Normal file
47
validator/keymanager/v2/types.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/shared/bls"
|
||||
)
|
||||
|
||||
// IKeymanager defines a general keymanager-v2 interface for Prysm wallets.
|
||||
type IKeymanager interface {
|
||||
// CreateAccount based on the keymanager's logic.
|
||||
CreateAccount(ctx context.Context, password string) error
|
||||
// MarshalConfigFile for the keymanager's options.
|
||||
MarshalConfigFile(ctx context.Context) ([]byte, error)
|
||||
// FetchValidatingKeys fetches the list of public keys that should be used to validate with.
|
||||
FetchValidatingPublicKeys() ([][48]byte, error)
|
||||
// Sign signs a message using a validator key.
|
||||
Sign(context.Context, interface{}) (bls.Signature, error)
|
||||
}
|
||||
|
||||
// Kind defines an enum for either direct, derived, or remote-signing
|
||||
// keystores for Prysm wallets.
|
||||
type Kind int
|
||||
|
||||
const (
|
||||
// Direct keymanager defines an on-disk, encrypted keystore-capable store.
|
||||
Direct Kind = iota
|
||||
// Derived keymanager using a hierarchical-deterministic algorithm.
|
||||
Derived
|
||||
// Remote keymanager capable of remote-signing data.
|
||||
Remote
|
||||
)
|
||||
|
||||
// String marshals a keymanager kind to a string value.
|
||||
func (k Kind) String() string {
|
||||
switch k {
|
||||
case Direct:
|
||||
return "direct"
|
||||
case Derived:
|
||||
return "derived"
|
||||
case Remote:
|
||||
return "remote"
|
||||
default:
|
||||
return fmt.Sprintf("%d", int(k))
|
||||
}
|
||||
}
|
||||
5
validator/keymanager/v2/types_test.go
Normal file
5
validator/keymanager/v2/types_test.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package v2
|
||||
|
||||
import "github.com/prysmaticlabs/prysm/validator/keymanager/v2/direct"
|
||||
|
||||
var _ = IKeymanager(&direct.Keymanager{})
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/shared/params"
|
||||
"github.com/prysmaticlabs/prysm/shared/version"
|
||||
v1 "github.com/prysmaticlabs/prysm/validator/accounts/v1"
|
||||
v2 "github.com/prysmaticlabs/prysm/validator/accounts/v2"
|
||||
"github.com/prysmaticlabs/prysm/validator/client/streaming"
|
||||
"github.com/prysmaticlabs/prysm/validator/flags"
|
||||
"github.com/prysmaticlabs/prysm/validator/node"
|
||||
@@ -101,6 +102,7 @@ func main() {
|
||||
app.Version = version.GetVersion()
|
||||
app.Action = startNode
|
||||
app.Commands = []*cli.Command{
|
||||
v2.Commands,
|
||||
{
|
||||
Name: "accounts",
|
||||
Category: "accounts",
|
||||
|
||||
Reference in New Issue
Block a user