Skip to content

Add mnemonics to registrar client #23

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
@@ -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=
Expand Down
31 changes: 18 additions & 13 deletions node-registrar/client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why passing mnemonic to the client?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's optional, user can initialize the client with or without the mnemonic, it the user already has an account, they can pass it to the client as in the tfpluginclient, if not they can just not pass mnemonic and has a client with no account.

if err != nil {
panic(err)
}
Expand Down
114 changes: 66 additions & 48 deletions node-registrar/client/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package client

import (
"bytes"
"crypto/ed25519"
"encoding/base64"
"encoding/json"
"fmt"
Expand All @@ -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,
Expand All @@ -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()

Expand All @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand All @@ -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)
Expand All @@ -200,44 +242,20 @@ 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 {
if c.twinID != 0 {
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")
}
Expand Down
28 changes: 14 additions & 14 deletions node-registrar/client/account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
})
Expand All @@ -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)
Expand All @@ -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"}
Expand All @@ -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"}
Expand All @@ -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)
Expand All @@ -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
Expand Down
Loading
Loading