diff --git a/go.work.sum b/go.work.sum index 3d14c0f..732a963 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,5 +1,6 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= diff --git a/node-registrar/client/README.md b/node-registrar/client/README.md index a900b55..b663e61 100644 --- a/node-registrar/client/README.md +++ b/node-registrar/client/README.md @@ -22,7 +22,7 @@ The Node Registrar Client enables communication with the ThreeFold Grid's node r * **Create Account**: Create new account on the registrar with uniqe key. * **Update Account**: Update the account configuration (relays & rmbEncKey). -* **Ensure Account**: Ensures that an account is created with specific seed. +* **Ensure Account**: Ensures that an account is created with specific seed/mnemonic. * **Get Account**: Get an account using either its twin\_id or its public\_key. ### Farms @@ -42,10 +42,10 @@ The Node Registrar Client enables communication with the ThreeFold Grid's node r #### Version Operations -| Method | Description | Parameters | Returns | -|-----------------|----------------------------------|----------------------------|---------------------| -| GetZosVersion | Get current zos version | None | (ZosVersion, error) | -| SetZosVersion | Update zos version (admin-only) | version string, force bool | error | +| Method | Description | Parameters | Returns | +|-----------------|----------------------------------|------------------------------------|---------------------| +| GetZosVersion | Get current zos version | None | (ZosVersion, error) | +| SetZosVersion | Update zos version (admin-only) | version string, safeToUpgrade bool | error | #### Account Management @@ -95,15 +95,20 @@ import ( func main() { registrarURL := "https://registrar.dev4.grid.tf/v1" - s := make([]byte, 32) - _, err := rand.Read(s) - if err != nil { - log.Fatal().Err(err).Send() - } - seed = hex.EncodeToString(s) - fmt.Println("New Seed (Hex):", seed) + // Generate 128-bit entropy (12-word mnemonic) + entropy, err := bip39.NewEntropy(128) + if err != nil { + panic(err) + } + + // Generate mnemonic from entropy + mnemonic, err = bip39.NewMnemonic(entropy) + if err != nil { + panic(err) + } + fmt.Println("New Mnemonic:", mnemonic) - cli, err := client.NewRegistrarClient(registrarURL, s) + cli, err := client.NewRegistrarClient(registrarURL, mnemonic) if err != nil { panic(err) } diff --git a/node-registrar/client/account.go b/node-registrar/client/account.go index 890dce9..f671105 100644 --- a/node-registrar/client/account.go +++ b/node-registrar/client/account.go @@ -2,7 +2,6 @@ package client import ( "bytes" - "crypto/ed25519" "encoding/base64" "encoding/json" "fmt" @@ -11,44 +10,83 @@ import ( "time" "github.com/pkg/errors" + "github.com/vedhavyas/go-subkey/v2" ) var ErrorAccountNotFround = fmt.Errorf("failed to get requested account from node regiatrar") -func (c RegistrarClient) CreateAccount(relays []string, rmbEncKey string) (account Account, err error) { +func (c *RegistrarClient) CreateAccount(relays []string, rmbEncKey string) (account Account, mnemonic string, err error) { return c.createAccount(relays, rmbEncKey) } -func (c RegistrarClient) GetAccount(id uint64) (account Account, err error) { +func (c *RegistrarClient) GetAccount(id uint64) (account Account, err error) { return c.getAccount(id) } -func (c RegistrarClient) GetAccountByPK(pk []byte) (account Account, err error) { +func (c *RegistrarClient) GetAccountByPK(pk []byte) (account Account, err error) { return c.getAccountByPK(pk) } -func (c RegistrarClient) UpdateAccount(opts ...UpdateAccountOpts) (err error) { +func (c *RegistrarClient) UpdateAccount(opts ...UpdateAccountOpts) (err error) { return c.updateAccount(opts) } -func (c RegistrarClient) EnsureAccount(relays []string, rmbEncKey string) (account Account, err error) { +type accountCfg struct { + relays []string + rmbEncKey string +} + +type ( + UpdateAccountOpts func(*accountCfg) +) + +func UpdateAccountWithRelays(relays []string) UpdateAccountOpts { + return func(n *accountCfg) { + n.relays = relays + } +} + +func UpdateAccountWithRMBEncKey(rmbEncKey string) UpdateAccountOpts { + return func(n *accountCfg) { + n.rmbEncKey = rmbEncKey + } +} + +func (c *RegistrarClient) EnsureAccount(relays []string, rmbEncKey string) (account Account, err error) { return c.ensureAccount(relays, rmbEncKey) } -func (c *RegistrarClient) createAccount(relays []string, rmbEncKey string) (account Account, err error) { +func (c *RegistrarClient) createAccount(relays []string, rmbEncKey string) (account Account, mnemonic string, err error) { url, err := url.JoinPath(c.baseURL, "accounts") if err != nil { - return account, errors.Wrap(err, "failed to construct registrar url") + return account, mnemonic, errors.Wrap(err, "failed to construct registrar url") } - timestamp := time.Now().Unix() - publicKeyBase64 := base64.StdEncoding.EncodeToString(c.keyPair.publicKey) + var keyPair subkey.KeyPair + if len(c.mnemonic) != 0 { + mnemonic = c.mnemonic + keyPair, err = parseKeysFromMnemonicOrSeed(c.mnemonic) + } else { + mnemonic, keyPair, err = generateNewMnemonic() + } + if err != nil { + return account, mnemonic, err + } + c.keyPair = keyPair + c.mnemonic = mnemonic + + publicKeyBase64 := base64.StdEncoding.EncodeToString(c.keyPair.Public()) + + timestamp := time.Now().Unix() challenge := []byte(fmt.Sprintf("%d:%v", timestamp, publicKeyBase64)) - signature := ed25519.Sign(c.keyPair.privateKey, challenge) + signature, err := keyPair.Sign(challenge) + if err != nil { + return account, mnemonic, errors.Wrap(err, "failed to sign account creation request") + } data := map[string]any{ - "public_key": c.keyPair.publicKey, + "public_key": c.keyPair.Public(), "signature": signature, "timestamp": timestamp, "rmb_enc_key": rmbEncKey, @@ -58,17 +96,17 @@ func (c *RegistrarClient) createAccount(relays []string, rmbEncKey string) (acco var body bytes.Buffer err = json.NewEncoder(&body).Encode(data) if err != nil { - return account, errors.Wrap(err, "failed to parse request body") + return account, mnemonic, errors.Wrap(err, "failed to parse request body") } resp, err := c.httpClient.Post(url, "application/json", &body) if err != nil { - return account, errors.Wrap(err, "failed to send request to the registrar") + return account, mnemonic, errors.Wrap(err, "failed to send request to the registrar") } if resp.StatusCode != http.StatusCreated { err = parseResponseError(resp.Body) - return account, errors.Wrapf(err, "failed to create account with status %s", resp.Status) + return account, mnemonic, errors.Wrapf(err, "failed to create account with status %s", resp.Status) } defer resp.Body.Close() @@ -78,7 +116,7 @@ func (c *RegistrarClient) createAccount(relays []string, rmbEncKey string) (acco return } -func (c RegistrarClient) getAccount(id uint64) (account Account, err error) { +func (c *RegistrarClient) getAccount(id uint64) (account Account, err error) { url, err := url.JoinPath(c.baseURL, "accounts") if err != nil { return account, errors.Wrap(err, "failed to construct registrar url") @@ -116,7 +154,7 @@ func (c RegistrarClient) getAccount(id uint64) (account Account, err error) { return } -func (c RegistrarClient) getAccountByPK(pk []byte) (account Account, err error) { +func (c *RegistrarClient) getAccountByPK(pk []byte) (account Account, err error) { url, err := url.JoinPath(c.baseURL, "accounts") if err != nil { return account, errors.Wrap(err, "failed to construct registrar url") @@ -157,7 +195,7 @@ func (c RegistrarClient) getAccountByPK(pk []byte) (account Account, err error) return account, err } -func (c RegistrarClient) updateAccount(opts []UpdateAccountOpts) (err error) { +func (c *RegistrarClient) updateAccount(opts []UpdateAccountOpts) (err error) { err = c.ensureTwinID() if err != nil { return errors.Wrap(err, "failed to ensure twin id") @@ -180,7 +218,11 @@ func (c RegistrarClient) updateAccount(opts []UpdateAccountOpts) (err error) { return } - req.Header.Set("X-Auth", c.signRequest(time.Now().Unix())) + authHeader, err := c.signRequest(time.Now().Unix()) + if err != nil { + return errors.Wrap(err, "failed to sign request") + } + req.Header.Set("X-Auth", authHeader) req.Header.Set("Content-Type", "application/json") resp, err := c.httpClient.Do(req) @@ -200,36 +242,12 @@ func (c RegistrarClient) updateAccount(opts []UpdateAccountOpts) (err error) { return } -type accountCfg struct { - relays []string - rmbEncKey string -} - -type ( - UpdateAccountOpts func(*accountCfg) -) - -func UpdateAccountWithRelays(relays []string) UpdateAccountOpts { - return func(n *accountCfg) { - n.relays = relays - } -} - -func UpdateAccountWithRMBEncKey(rmbEncKey string) UpdateAccountOpts { - return func(n *accountCfg) { - n.rmbEncKey = rmbEncKey - } -} - -func (c RegistrarClient) ensureAccount(relays []string, rmbEncKey string) (account Account, err error) { - account, err = c.GetAccountByPK(c.keyPair.publicKey) +func (c *RegistrarClient) ensureAccount(relays []string, rmbEncKey string) (account Account, err error) { + account, err = c.GetAccountByPK(c.keyPair.Public()) if errors.Is(err, ErrorAccountNotFround) { - return c.CreateAccount(relays, rmbEncKey) - } else if err != nil { - return account, errors.Wrap(err, "failed to get account from the registrar") + account, _, err = c.CreateAccount(relays, rmbEncKey) } - - return + return account, err } func (c *RegistrarClient) ensureTwinID() error { @@ -237,7 +255,7 @@ func (c *RegistrarClient) ensureTwinID() error { return nil } - twin, err := c.getAccountByPK(c.keyPair.publicKey) + twin, err := c.getAccountByPK(c.keyPair.Public()) if err != nil { return errors.Wrap(err, "failed to get the account of the node, registrar client was not set up properly") } diff --git a/node-registrar/client/account_test.go b/node-registrar/client/account_test.go index 76e4e8b..798ae4f 100644 --- a/node-registrar/client/account_test.go +++ b/node-registrar/client/account_test.go @@ -15,9 +15,9 @@ func TestCreateAccount(t *testing.T) { var count int require := require.New(t) - publicKey, privateKey, err := aliceKeys() + keyPair, err := parseKeysFromMnemonicOrSeed(testMnemonic) require.NoError(err) - account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) + account.PublicKey = base64.StdEncoding.EncodeToString(keyPair.Public()) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) @@ -32,12 +32,12 @@ func TestCreateAccount(t *testing.T) { require.NoError(err) request = newClientWithNoAccount - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) t.Run("test create account created successfully", func(t *testing.T) { request = createAccountStatusCreated - result, err := c.CreateAccount(account.Relays, account.RMBEncKey) + result, _, err := c.CreateAccount(account.Relays, account.RMBEncKey) require.NoError(err) require.Equal(account, result) }) @@ -48,9 +48,9 @@ func TestUpdateAccount(t *testing.T) { var count int require := require.New(t) - publicKey, privateKey, err := aliceKeys() + keyPair, err := parseKeysFromMnemonicOrSeed(testMnemonic) require.NoError(err) - account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) + account.PublicKey = base64.StdEncoding.EncodeToString(keyPair.Public()) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) @@ -68,11 +68,11 @@ func TestUpdateAccount(t *testing.T) { t.Run("test update account updated successfully", func(t *testing.T) { count = 0 request = newClientWithAccountNoNode - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) require.Equal(c.twinID, account.TwinID) - require.Equal(c.keyPair.publicKey, publicKey) + require.Equal(c.keyPair, keyPair) request = updateAccountWithStatusOK relays := []string{"relay1"} @@ -82,10 +82,10 @@ func TestUpdateAccount(t *testing.T) { t.Run("test update account account not found", func(t *testing.T) { request = newClientWithNoAccount - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) - require.Equal(c.keyPair.publicKey, publicKey) + require.Equal(c.keyPair, keyPair) request = updateAccountWithNoAccount relays := []string{"relay1"} @@ -99,9 +99,9 @@ func TestGetAccount(t *testing.T) { var count int require := require.New(t) - publicKey, privateKey, err := aliceKeys() + keyPair, err := parseKeysFromMnemonicOrSeed(testMnemonic) require.NoError(err) - account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) + account.PublicKey = base64.StdEncoding.EncodeToString(keyPair.Public()) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) @@ -117,10 +117,10 @@ func TestGetAccount(t *testing.T) { count = 0 request = newClientWithAccountNoNode - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) require.Equal(account.TwinID, c.twinID) - require.Equal(publicKey, c.keyPair.publicKey) + require.Equal(keyPair, c.keyPair) t.Run("test get account with id account not found", func(t *testing.T) { request = getAccountWithIDStatusNotFount diff --git a/node-registrar/client/client.go b/node-registrar/client/client.go index 0f3576f..57e3174 100644 --- a/node-registrar/client/client.go +++ b/node-registrar/client/client.go @@ -1,40 +1,42 @@ package client import ( - "crypto/ed25519" "net/http" "github.com/pkg/errors" + "github.com/vedhavyas/go-subkey" ) -type keyPair struct { - privateKey ed25519.PrivateKey - publicKey ed25519.PublicKey -} - type RegistrarClient struct { httpClient http.Client - keyPair keyPair + baseURL string + keyPair subkey.KeyPair + mnemonic string nodeID uint64 twinID uint64 - baseURL string } -func NewRegistrarClient(baseURL string, privateKey ed25519.PrivateKey) (cli RegistrarClient, err error) { +func NewRegistrarClient(baseURL string, mnemonicOrSeed ...string) (cli RegistrarClient, err error) { client := http.DefaultClient - publicKey, ok := privateKey.Public().(ed25519.PublicKey) - if !ok { - return cli, errors.Wrap(err, "failed to get public key of provided private key") - } - cli = RegistrarClient{ httpClient: *client, - keyPair: keyPair{privateKey, publicKey}, baseURL: baseURL, } - account, err := cli.GetAccountByPK(publicKey) + if len(mnemonicOrSeed) == 0 { + return cli, nil + } + + keyPair, err := parseKeysFromMnemonicOrSeed(mnemonicOrSeed[0]) + if err != nil { + return cli, errors.Wrapf(err, "Failed to derive key pair from mnemonic/seed phrase %s", mnemonicOrSeed[0]) + } + + cli.keyPair = keyPair + cli.mnemonic = mnemonicOrSeed[0] + + account, err := cli.GetAccountByPK(keyPair.Public()) if errors.Is(err, ErrorAccountNotFround) { return cli, nil } else if err != nil { diff --git a/node-registrar/client/client_test.go b/node-registrar/client/client_test.go index 187ee21..0a2a973 100644 --- a/node-registrar/client/client_test.go +++ b/node-registrar/client/client_test.go @@ -15,9 +15,9 @@ func TestNewRegistrarClient(t *testing.T) { var count int require := require.New(t) - publicKey, privateKey, err := aliceKeys() + keyPair, err := parseKeysFromMnemonicOrSeed(testMnemonic) require.NoError(err) - account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) + account.PublicKey = base64.StdEncoding.EncodeToString(keyPair.Public()) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) @@ -34,29 +34,30 @@ func TestNewRegistrarClient(t *testing.T) { t.Run("test new registrar client with no account", func(t *testing.T) { count = 0 request = newClientWithNoAccount - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) require.Equal(uint64(0), c.twinID) require.Equal(uint64(0), c.nodeID) - require.Equal(publicKey, c.keyPair.publicKey) + require.Equal(keyPair, c.keyPair) }) t.Run("test new registrar client with account and no node", func(t *testing.T) { count = 0 request = newClientWithAccountNoNode - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) require.Equal(account.TwinID, c.twinID) require.Equal(uint64(0), c.nodeID) - require.Equal(publicKey, c.keyPair.publicKey) + require.Equal(keyPair, c.keyPair) }) + t.Run("test new registrar client with account and node", func(t *testing.T) { count = 0 request = newClientWithAccountAndNode - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) require.Equal(account.TwinID, c.twinID) require.Equal(nodeID, c.nodeID) - require.Equal(publicKey, c.keyPair.publicKey) + require.Equal(keyPair, c.keyPair) }) } diff --git a/node-registrar/client/farm.go b/node-registrar/client/farm.go index 1e718d2..2dedbc9 100644 --- a/node-registrar/client/farm.go +++ b/node-registrar/client/farm.go @@ -13,19 +13,19 @@ import ( var ErrorFarmNotFround = fmt.Errorf("failed to get requested farm from node regiatrar") -func (c RegistrarClient) CreateFarm(farmName string, dedicated bool) (farmID uint64, err error) { +func (c *RegistrarClient) CreateFarm(farmName string, dedicated bool) (farmID uint64, err error) { return c.createFarm(farmName, dedicated) } -func (c RegistrarClient) UpdateFarm(farmID uint64, opts ...UpdateFarmOpts) (err error) { +func (c *RegistrarClient) UpdateFarm(farmID uint64, opts ...UpdateFarmOpts) (err error) { return c.updateFarm(farmID, opts) } -func (c RegistrarClient) GetFarm(id uint64) (farm Farm, err error) { +func (c *RegistrarClient) GetFarm(id uint64) (farm Farm, err error) { return c.getFarm(id) } -func (c RegistrarClient) ListFarms(opts ...ListFarmOpts) (farms []Farm, err error) { +func (c *RegistrarClient) ListFarms(opts ...ListFarmOpts) (farms []Farm, err error) { return c.listFarms(opts...) } @@ -91,7 +91,7 @@ func UpdateFarmWithDedicated() UpdateFarmOpts { } } -func (c RegistrarClient) createFarm(farmName string, dedicated bool) (farmID uint64, err error) { +func (c *RegistrarClient) createFarm(farmName string, dedicated bool) (farmID uint64, err error) { err = c.ensureTwinID() if err != nil { return farmID, errors.Wrap(err, "failed to ensure twin id") @@ -119,7 +119,11 @@ func (c RegistrarClient) createFarm(farmName string, dedicated bool) (farmID uin return farmID, errors.Wrap(err, "failed to construct http request to the registrar") } - req.Header.Set("X-Auth", c.signRequest(time.Now().Unix())) + authHeader, err := c.signRequest(time.Now().Unix()) + if err != nil { + return farmID, errors.Wrap(err, "failed to sign request") + } + req.Header.Set("X-Auth", authHeader) req.Header.Set("Content-Type", "application/json") resp, err := c.httpClient.Do(req) @@ -145,7 +149,7 @@ func (c RegistrarClient) createFarm(farmName string, dedicated bool) (farmID uin return result.FarmID, nil } -func (c RegistrarClient) updateFarm(farmID uint64, opts []UpdateFarmOpts) (err error) { +func (c *RegistrarClient) updateFarm(farmID uint64, opts []UpdateFarmOpts) (err error) { err = c.ensureTwinID() if err != nil { return errors.Wrap(err, "failed to ensure twin id") @@ -169,7 +173,11 @@ func (c RegistrarClient) updateFarm(farmID uint64, opts []UpdateFarmOpts) (err e return errors.Wrap(err, "failed to construct http request to the registrar") } - req.Header.Set("X-Auth", c.signRequest(time.Now().Unix())) + authHeader, err := c.signRequest(time.Now().Unix()) + if err != nil { + return errors.Wrap(err, "failed to sign request") + } + req.Header.Set("X-Auth", authHeader) req.Header.Set("Content-Type", "application/json") resp, err := c.httpClient.Do(req) @@ -187,7 +195,7 @@ func (c RegistrarClient) updateFarm(farmID uint64, opts []UpdateFarmOpts) (err e return } -func (c RegistrarClient) getFarm(id uint64) (farm Farm, err error) { +func (c *RegistrarClient) getFarm(id uint64) (farm Farm, err error) { url, err := url.JoinPath(c.baseURL, "farms", fmt.Sprint(id)) if err != nil { return farm, errors.Wrap(err, "failed to construct registrar url") @@ -215,7 +223,7 @@ func (c RegistrarClient) getFarm(id uint64) (farm Farm, err error) { return } -func (c RegistrarClient) listFarms(opts ...ListFarmOpts) (farms []Farm, err error) { +func (c *RegistrarClient) listFarms(opts ...ListFarmOpts) (farms []Farm, err error) { url, err := url.JoinPath(c.baseURL, "farms") if err != nil { return farms, errors.Wrap(err, "failed to construct registrar url") diff --git a/node-registrar/client/farm_test.go b/node-registrar/client/farm_test.go index 1ce134b..4a4bae3 100644 --- a/node-registrar/client/farm_test.go +++ b/node-registrar/client/farm_test.go @@ -15,9 +15,9 @@ func TestCreateFarm(t *testing.T) { var count int require := require.New(t) - publicKey, privateKey, err := aliceKeys() + keyPair, err := parseKeysFromMnemonicOrSeed(testMnemonic) require.NoError(err) - account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) + account.PublicKey = base64.StdEncoding.EncodeToString(keyPair.Public()) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) @@ -32,7 +32,7 @@ func TestCreateFarm(t *testing.T) { require.NoError(err) request = newClientWithAccountNoNode - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) t.Run("test create farm with status conflict", func(t *testing.T) { @@ -54,9 +54,9 @@ func TestUpdateFarm(t *testing.T) { var count int require := require.New(t) - publicKey, privateKey, err := aliceKeys() + keyPair, err := parseKeysFromMnemonicOrSeed(testMnemonic) require.NoError(err) - account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) + account.PublicKey = base64.StdEncoding.EncodeToString(keyPair.Public()) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) @@ -72,7 +72,7 @@ func TestUpdateFarm(t *testing.T) { t.Run("test update farm with status unauthorzed", func(t *testing.T) { request = newClientWithNoAccount - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) request = updateFarmWithStatusUnauthorized @@ -83,7 +83,7 @@ func TestUpdateFarm(t *testing.T) { t.Run("test update farm with status ok", func(t *testing.T) { count = 0 request = newClientWithAccountNoNode - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) request = updateFarmWithStatusOK @@ -97,9 +97,9 @@ func TestGetFarm(t *testing.T) { var count int require := require.New(t) - publicKey, privateKey, err := aliceKeys() + keyPair, err := parseKeysFromMnemonicOrSeed(testMnemonic) require.NoError(err) - account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) + account.PublicKey = base64.StdEncoding.EncodeToString(keyPair.Public()) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) @@ -115,7 +115,7 @@ func TestGetFarm(t *testing.T) { count = 0 request = newClientWithAccountNoNode - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) t.Run("test get farm with status not found", func(t *testing.T) { diff --git a/node-registrar/client/mnemonic.go b/node-registrar/client/mnemonic.go new file mode 100644 index 0000000..f4cbb46 --- /dev/null +++ b/node-registrar/client/mnemonic.go @@ -0,0 +1,44 @@ +package client + +import ( + "github.com/cosmos/go-bip39" + "github.com/pkg/errors" + subkeyEd25519 "github.com/vedhavyas/go-subkey/v2/ed25519" + + "github.com/vedhavyas/go-subkey/v2" +) + +func (c *RegistrarClient) Mnemonic() string { + return c.mnemonic +} + +func parseKeysFromMnemonicOrSeed(mnemonicOrSeed string) (keypair subkey.KeyPair, err error) { + // otherwise drive key pair from seed + keypair, err = subkey.DeriveKeyPair(subkeyEd25519.Scheme{}, mnemonicOrSeed) + if err != nil { + return keypair, errors.Wrapf(err, "failed to derive key pair from seed %s", mnemonicOrSeed) + } + + return keypair, nil +} + +func generateNewMnemonic() (mnemonic string, keypair subkey.KeyPair, err error) { + // Generate 128-bit entropy (12-word mnemonic) + entropy, err := bip39.NewEntropy(128) + if err != nil { + return mnemonic, keypair, errors.Wrap(err, "failed to generate entropy") + } + + // Generate mnemonic from entropy + mnemonic, err = bip39.NewMnemonic(entropy) + if err != nil { + return mnemonic, keypair, errors.Wrap(err, "failed to generate mnemonic") + } + + // Drive key pair from mnemonic + keypair, err = subkey.DeriveKeyPair(subkeyEd25519.Scheme{}, mnemonic) + if err != nil { + return mnemonic, keypair, errors.Wrapf(err, "failed to derive key pair from mnemonic phrase %s", mnemonic) + } + return +} diff --git a/node-registrar/client/node.go b/node-registrar/client/node.go index e2d2bac..ee88731 100644 --- a/node-registrar/client/node.go +++ b/node-registrar/client/node.go @@ -14,7 +14,7 @@ import ( var ErrorNodeNotFround = fmt.Errorf("failed to get requested node from node regiatrar") -func (c RegistrarClient) RegisterNode( +func (c *RegistrarClient) RegisterNode( farmID uint64, twinID uint64, interfaces []Interface, @@ -27,23 +27,23 @@ func (c RegistrarClient) RegisterNode( return c.registerNode(farmID, twinID, interfaces, location, resources, serialNumber, secureBoot, virtualized) } -func (c RegistrarClient) UpdateNode(opts ...UpdateNodeOpts) (err error) { +func (c *RegistrarClient) UpdateNode(opts ...UpdateNodeOpts) (err error) { return c.updateNode(opts) } -func (c RegistrarClient) ReportUptime(report UptimeReport) (err error) { +func (c *RegistrarClient) ReportUptime(report UptimeReport) (err error) { return c.reportUptime(report) } -func (c RegistrarClient) GetNode(id uint64) (node Node, err error) { +func (c *RegistrarClient) GetNode(id uint64) (node Node, err error) { return c.getNode(id) } -func (c RegistrarClient) GetNodeByTwinID(id uint64) (node Node, err error) { +func (c *RegistrarClient) GetNodeByTwinID(id uint64) (node Node, err error) { return c.getNodeByTwinID(id) } -func (c RegistrarClient) ListNodes(opts ...ListNodeOpts) (nodes []Node, err error) { +func (c *RegistrarClient) ListNodes(opts ...ListNodeOpts) (nodes []Node, err error) { return c.listNodes(opts) } @@ -166,7 +166,7 @@ func UpdateNodeWithHealthy() UpdateNodeOpts { } } -func (c RegistrarClient) registerNode( +func (c *RegistrarClient) registerNode( farmID uint64, twinID uint64, interfaces []Interface, @@ -207,7 +207,11 @@ func (c RegistrarClient) registerNode( return nodeID, errors.Wrap(err, "failed to construct http request to the registrar") } - req.Header.Set("X-Auth", c.signRequest(time.Now().Unix())) + authHeader, err := c.signRequest(time.Now().Unix()) + if err != nil { + return nodeID, errors.Wrap(err, "failed to sign request") + } + req.Header.Set("X-Auth", authHeader) req.Header.Set("Content-Type", "application/json") resp, err := c.httpClient.Do(req) @@ -231,7 +235,7 @@ func (c RegistrarClient) registerNode( return result.NodeID, err } -func (c RegistrarClient) updateNode(opts []UpdateNodeOpts) (err error) { +func (c *RegistrarClient) updateNode(opts []UpdateNodeOpts) (err error) { err = c.ensureNodeID() if err != nil { return err @@ -260,7 +264,11 @@ func (c RegistrarClient) updateNode(opts []UpdateNodeOpts) (err error) { return errors.Wrap(err, "failed to construct http request to the registrar") } - req.Header.Set("X-Auth", c.signRequest(time.Now().Unix())) + authHeader, err := c.signRequest(time.Now().Unix()) + if err != nil { + return errors.Wrap(err, "failed to sign request") + } + req.Header.Set("X-Auth", authHeader) req.Header.Set("Content-Type", "application/json") resp, err := c.httpClient.Do(req) @@ -277,7 +285,7 @@ func (c RegistrarClient) updateNode(opts []UpdateNodeOpts) (err error) { return } -func (c RegistrarClient) reportUptime(report UptimeReport) (err error) { +func (c *RegistrarClient) reportUptime(report UptimeReport) (err error) { err = c.ensureNodeID() if err != nil { return err @@ -300,7 +308,11 @@ func (c RegistrarClient) reportUptime(report UptimeReport) (err error) { return errors.Wrap(err, "failed to construct http request to the registrar") } - req.Header.Set("X-Auth", c.signRequest(time.Now().Unix())) + authHeader, err := c.signRequest(time.Now().Unix()) + if err != nil { + return errors.Wrap(err, "failed to sign request") + } + req.Header.Set("X-Auth", authHeader) req.Header.Set("Content-Type", "application/json") resp, err := c.httpClient.Do(req) @@ -317,7 +329,7 @@ func (c RegistrarClient) reportUptime(report UptimeReport) (err error) { return } -func (c RegistrarClient) getNode(id uint64) (node Node, err error) { +func (c *RegistrarClient) getNode(id uint64) (node Node, err error) { url, err := url.JoinPath(c.baseURL, "nodes", fmt.Sprint(id)) if err != nil { return node, errors.Wrap(err, "failed to construct registrar url") @@ -346,7 +358,7 @@ func (c RegistrarClient) getNode(id uint64) (node Node, err error) { return } -func (c RegistrarClient) getNodeByTwinID(id uint64) (node Node, err error) { +func (c *RegistrarClient) getNodeByTwinID(id uint64) (node Node, err error) { nodes, err := c.ListNodes(ListNodesWithTwinID(id)) if err != nil { return @@ -359,7 +371,7 @@ func (c RegistrarClient) getNodeByTwinID(id uint64) (node Node, err error) { return nodes[0], nil } -func (c RegistrarClient) listNodes(opts []ListNodeOpts) (nodes []Node, err error) { +func (c *RegistrarClient) listNodes(opts []ListNodeOpts) (nodes []Node, err error) { url, err := url.JoinPath(c.baseURL, "nodes") if err != nil { return nodes, errors.Wrap(err, "failed to construct registrar url") @@ -425,7 +437,7 @@ func (c *RegistrarClient) ensureNodeID() error { return nil } -func (c RegistrarClient) parseUpdateNodeOpts(node Node, opts []UpdateNodeOpts) Node { +func (c *RegistrarClient) parseUpdateNodeOpts(node Node, opts []UpdateNodeOpts) Node { cfg := nodeCfg{ farmID: 0, Location: Location{}, diff --git a/node-registrar/client/node_test.go b/node-registrar/client/node_test.go index b39318b..1028054 100644 --- a/node-registrar/client/node_test.go +++ b/node-registrar/client/node_test.go @@ -1,7 +1,6 @@ package client import ( - "encoding/base64" "net/http" "net/http/httptest" "net/url" @@ -16,10 +15,6 @@ func TestRegistarNode(t *testing.T) { var count int require := require.New(t) - publicKey, privateKey, err := aliceKeys() - require.NoError(err) - account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) - testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) w.WriteHeader(statusCode) @@ -34,7 +29,7 @@ func TestRegistarNode(t *testing.T) { t.Run("test registar node no account", func(t *testing.T) { request = newClientWithNoAccount - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) request = registerNodeWithNoAccount @@ -45,7 +40,7 @@ func TestRegistarNode(t *testing.T) { t.Run("test registar node, node already exist", func(t *testing.T) { count = 0 request = newClientWithAccountAndNode - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) count = 0 @@ -57,7 +52,7 @@ func TestRegistarNode(t *testing.T) { t.Run("test registar node, created successfully", func(t *testing.T) { count = 0 request = newClientWithAccountNoNode - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) count = 0 @@ -73,10 +68,10 @@ func TestUpdateNode(t *testing.T) { var count int require := require.New(t) - publicKey, privateKey, err := aliceKeys() - require.NoError(err) - account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) - + // publicKey, privateKey, err := aliceKeys() + // require.NoError(err) + // account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) + // testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) w.WriteHeader(statusCode) @@ -90,7 +85,7 @@ func TestUpdateNode(t *testing.T) { require.NoError(err) request = newClientWithAccountAndNode - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) t.Run("test update node with status ok", func(t *testing.T) { @@ -117,9 +112,9 @@ func TestGetNode(t *testing.T) { var count int require := require.New(t) - publicKey, privateKey, err := aliceKeys() - require.NoError(err) - account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) + // publicKey, privateKey, err := aliceKeys() + // require.NoError(err) + // account.PublicKey = base64.StdEncoding.EncodeToString(publicKey) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { statusCode, body := serverHandler(r, request, count, require) @@ -134,7 +129,7 @@ func TestGetNode(t *testing.T) { require.NoError(err) request = newClientWithAccountAndNode - c, err := NewRegistrarClient(baseURL, privateKey) + c, err := NewRegistrarClient(baseURL, testMnemonic) require.NoError(err) t.Run("test get node status not found", func(t *testing.T) { diff --git a/node-registrar/client/utils.go b/node-registrar/client/utils.go index 3cf3995..3d64cba 100644 --- a/node-registrar/client/utils.go +++ b/node-registrar/client/utils.go @@ -1,7 +1,6 @@ package client import ( - "crypto/ed25519" "encoding/base64" "encoding/json" "fmt" @@ -10,9 +9,12 @@ import ( "github.com/pkg/errors" ) -func (c RegistrarClient) signRequest(timestamp int64) (authHeader string) { +func (c *RegistrarClient) signRequest(timestamp int64) (authHeader string, err error) { challenge := []byte(fmt.Sprintf("%d:%v", timestamp, c.twinID)) - signature := ed25519.Sign(c.keyPair.privateKey, challenge) + signature, err := c.keyPair.Sign(challenge) + if err != nil { + return "", err + } authHeader = fmt.Sprintf( "%s:%s", diff --git a/node-registrar/client/utils_test.go b/node-registrar/client/utils_test.go index bdb83b3..80b5d1a 100644 --- a/node-registrar/client/utils_test.go +++ b/node-registrar/client/utils_test.go @@ -1,8 +1,6 @@ package client import ( - "crypto/ed25519" - "encoding/hex" "encoding/json" "fmt" "net/http" @@ -11,10 +9,9 @@ import ( ) var ( - aliceSeed = "e5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a" - account = Account{TwinID: 1, Relays: []string{}, RMBEncKey: ""} - farm = Farm{FarmID: 1, FarmName: "freeFarm", TwinID: 1} - node = Node{NodeID: 1, FarmID: farmID, TwinID: twinID} + account = Account{TwinID: 1, Relays: []string{}, RMBEncKey: ""} + farm = Farm{FarmID: 1, FarmName: "freeFarm", TwinID: 1} + node = Node{NodeID: 1, FarmID: farmID, TwinID: twinID} ) const ( @@ -47,6 +44,8 @@ const ( getNodeWithTwinID listNodesInFarm + testMnemonic = "bottom drive obey lake curtain smoke basket hold race lonely fit walk" + farmID uint64 = 1 nodeID uint64 = 1 twinID uint64 = 1 @@ -236,18 +235,3 @@ func serverHandler(r *http.Request, request, count int, require *require.Asserti return http.StatusNotAcceptable, nil } - -func aliceKeys() (publicKey ed25519.PublicKey, privateKey ed25519.PrivateKey, err error) { - seed, err := hex.DecodeString(aliceSeed) - if err != nil { - return - } - - privateKey = ed25519.NewKeyFromSeed(seed) - publicKey, ok := privateKey.Public().(ed25519.PublicKey) - if !ok { - return publicKey, privateKey, fmt.Errorf("failed to get public key of provided private key") - } - - return -} diff --git a/node-registrar/client/zos_version.go b/node-registrar/client/zos_version.go index 9351261..05547fa 100644 --- a/node-registrar/client/zos_version.go +++ b/node-registrar/client/zos_version.go @@ -12,15 +12,15 @@ import ( "github.com/pkg/errors" ) -func (c RegistrarClient) GetZosVersion() (version ZosVersion, err error) { +func (c *RegistrarClient) GetZosVersion() (version ZosVersion, err error) { return c.getZosVersion() } -func (c RegistrarClient) SetZosVersion(v string, safeToUpgrade bool) (err error) { +func (c *RegistrarClient) SetZosVersion(v string, safeToUpgrade bool) (err error) { return c.setZosVersion(v, safeToUpgrade) } -func (c RegistrarClient) getZosVersion() (version ZosVersion, err error) { +func (c *RegistrarClient) getZosVersion() (version ZosVersion, err error) { url, err := url.JoinPath(c.baseURL, "zos", "version") if err != nil { return version, errors.Wrap(err, "failed to construct registrar url") @@ -59,7 +59,7 @@ func (c RegistrarClient) getZosVersion() (version ZosVersion, err error) { return } -func (c RegistrarClient) setZosVersion(v string, safeToUpgrade bool) (err error) { +func (c *RegistrarClient) setZosVersion(v string, safeToUpgrade bool) (err error) { err = c.ensureTwinID() if err != nil { return errors.Wrap(err, "failed to ensure twin id") @@ -96,7 +96,11 @@ func (c RegistrarClient) setZosVersion(v string, safeToUpgrade bool) (err error) return errors.Wrap(err, "failed to construct http request to the registrar") } - req.Header.Set("X-Auth", c.signRequest(time.Now().Unix())) + authHeader, err := c.signRequest(time.Now().Unix()) + if err != nil { + return errors.Wrap(err, "failed to sign request") + } + req.Header.Set("X-Auth", authHeader) req.Header.Set("Content-Type", "application/json") resp, err := c.httpClient.Do(req) diff --git a/node-registrar/go.mod b/node-registrar/go.mod index 7ec9c7c..bbd37d0 100644 --- a/node-registrar/go.mod +++ b/node-registrar/go.mod @@ -61,6 +61,7 @@ require ( github.com/stretchr/testify v1.10.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect + github.com/vedhavyas/go-subkey v1.0.4 // indirect golang.org/x/arch v0.12.0 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/net v0.33.0 // indirect diff --git a/node-registrar/go.sum b/node-registrar/go.sum index 336ff8a..d24bbde 100644 --- a/node-registrar/go.sum +++ b/node-registrar/go.sum @@ -158,6 +158,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/vedhavyas/go-subkey v1.0.4 h1:QwjBZx4w7qXC2lmqol2jJfhaNXPI9BsgLZiMiCwqGDU= +github.com/vedhavyas/go-subkey v1.0.4/go.mod h1:aOIil/KS9hJlnr9ZSQKSoXdu/MbnkCxG4x9IOlLsMtI= github.com/vedhavyas/go-subkey/v2 v2.0.0 h1:LemDIsrVtRSOkp0FA8HxP6ynfKjeOj3BY2U9UNfeDMA= github.com/vedhavyas/go-subkey/v2 v2.0.0/go.mod h1:95aZ+XDCWAUUynjlmi7BtPExjXgXxByE0WfBwbmIRH4= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=