diff --git a/.goreleaser.yaml b/.goreleaser.yaml index e02fbae..039a915 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -18,6 +18,32 @@ builds: - -X github.com/threefoldtech/tfgrid4-sdk-go/node-registrar/cmd.version={{.Tag}} - -X github.com/threefoldtech/tfgrid-sdk-go/node-registrar/cmd.commit={{.Commit}} + - dir: ./node-registrar/tools/account + env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin + binary: new-account + id: new-account + + ignore: + - goos: windows + + - dir: ./node-registrar/tools/farm + env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin + binary: new-farm + id: new-farm + + ignore: + - goos: windows + archives: - format: tar.gz diff --git a/node-registrar/docs/README.md b/node-registrar/docs/README.md new file mode 100644 index 0000000..6163249 --- /dev/null +++ b/node-registrar/docs/README.md @@ -0,0 +1,322 @@ +# Node Registrar client + +To be able to use the node registrar you can use the following scripts. + +## Account Management + +- **Create a Seed:** + +```go +// Generate new seed +seed := make([]byte, 32) +_, err := rand.Read(seed) +if err != nil { + panic(err) +} + +hexKey := hex.EncodeToString(seed) +fmt.Println("New Seed (Hex):", hexKey) + +``` + +- **Parse The Seed** + +```go +// Generate Key Pair +privateKey := ed25519.NewKeyFromSeed(seed) +publicKey := privateKey.Public().(ed25519.PublicKey) + +fmt.Println("Private Key (Hex):", hex.EncodeToString(privateKey)) +fmt.Println("Public Key (Hex):", hex.EncodeToString(publicKey)) + +``` + +- **Create Account** + +```go + url, err := url.JoinPath(registrarURL, "accounts") + if err != nil { + panic(err) + } + + timestamp := time.Now().Unix() + publicKeyBase64 := base64.StdEncoding.EncodeToString(publicKey) + + challenge := []byte(fmt.Sprintf("%d:%v", timestamp, publicKeyBase64)) + signature := ed25519.Sign(privateKey, challenge) + + data := map[string]any{ + "public_key": publicKey, + "signature": signature, + "timestamp": timestamp, + "rmb_enc_key": rmbEncKey, + "relays": relays, + } + + var body bytes.Buffer + err = json.NewEncoder(&body).Encode(data) + if err != nil { + panic(err) + } + + resp, err := http.DefaultClient.Post(url, "application/json", &body) + if err != nil { + panic(err) + } + + if resp.StatusCode != http.StatusCreated { + panic(fmt.Errorf("account not created successfully")) + } + + defer resp.Body.Close() + + var account map[string]any + err = json.NewDecoder(resp.Body).Decode(&account) + + fmt.Println(account["twin_id"]) +``` + +- **Get Account:** + +```go + url, err := url.JoinPath(registrarURL, "accounts") + if err != nil { + panic(err) + } + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return + } + + q := req.URL.Query() + q.Add("twin_id", fmt.Sprint(twinID)) + req.URL.RawQuery = q.Encode() + + resp, err := http.DefaultClient.Do(req) + if err != nil { + panic(err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusNotFound { + panic(fmt.Errorf("status code not ok")) + } + + var account map[string]any + err = json.NewDecoder(resp.Body).Decode(&account) + fmt.Println(account) +``` + +## Farm Management + +- **Create a Farm:** + +```go + url, err := url.JoinPath(registrarURL, "farms") + if err != nil { + panic(err) + } + + data := map[string]any{ + "farm_name": farmName, + "twin_id": twinID, + "dedicated": dedicated, + } + + var body bytes.Buffer + err = json.NewEncoder(&body).Encode(data) + if err != nil { + panic(err) + } + + req, err := http.NewRequest("POST", url, &body) + if err != nil { + panic(err) + } + + timestamp := time.Now().Unix() + challenge := []byte(fmt.Sprintf("%d:%v", timestamp, twinID)) + signature := ed25519.Sign(privateKey, challenge) + + authHeader := fmt.Sprintf( + "%s:%s", + base64.StdEncoding.EncodeToString(challenge), + base64.StdEncoding.EncodeToString(signature), + ) + req.Header.Set("X-Auth", authHeader) + req.Header.Set("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + panic(err) + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusCreated { + panic(err) + } + + result := struct { + FarmID uint64 `json:"farm_id"` + }{} + + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + panic(err) + } + + fmt.Println(result.FarmID) +``` + +- **Get Farm:** + +```go + url, err := url.JoinPath(registrarURL, "farms", fmt.Sprint(farmID)) + if err != nil { + panic(err) + } + resp, err := http.DefaultClient.Get(url) + if err != nil { + panic(err) + } + + if resp.StatusCode != http.StatusOK { + panic(err) + } + defer resp.Body.Close() + + var result map[string]any + err = json.NewDecoder(resp.Body).Decode(&result) + if err != nil { + panic(err) + } + + fmt.Println(result) +``` + +## Zos Version Management + +- **Get Zos Version:** + +```go + url, err := url.JoinPath(registrarURL, "zos", "version") + if err != nil { + panic(err) + } + + resp, err := http.DefaultClient.Get(url) + if err != nil { + panic(err) + } + + if resp.StatusCode != http.StatusOK { + panic(err) + } + + defer resp.Body.Close() + + var versionString string + err = json.NewDecoder(resp.Body).Decode(&versionString) + if err != nil { + panic(err) + } + + versionBytes, err := base64.StdEncoding.DecodeString(versionString) + if err != nil { + panic(err) + } + + correctedJSON := strings.ReplaceAll(string(versionBytes), "'", "\"") + + var version map[string]any + err = json.NewDecoder(strings.NewReader(correctedJSON)).Decode(&version) + if err != nil { + panic(err) + } + + fmt.Println(version) +``` + +- **Set Zos Version** +To set zos version you need to have the seed to the admin account + +```go + url, err := url.JoinPath(registrarURL, "zos", "version") + if err != nil { + panic(err) + } + + v := "{'safe_to_upgrade': true, 'version':'v0.1.7'}" + + version := struct { + Version string `json:"version"` + }{ + Version: base64.StdEncoding.EncodeToString([]byte(v)), + } + + body, err := json.Marshal(version) + if err != nil { + panic(err) + } + + // Create auth headers + timestamp := time.Now().Unix() + challenge := []byte(fmt.Sprintf("%d:%v", timestamp, twinID)) + signature := ed25519.Sign(privateKey, challenge) + + req, err := http.NewRequest("PUT", url, bytes.NewReader(body)) + if err != nil { + panic(err) + } + + // Set required headers + authHeader := fmt.Sprintf( + "%s:%s", + base64.StdEncoding.EncodeToString(challenge), + base64.StdEncoding.EncodeToString(signature), + ) + req.Header.Set("X-Auth", authHeader) + req.Header.Set("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + panic(err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + panic(err) + } + + fmt.Println("version updated successfully: ", string(body)) +``` + +## Node Management + +- **Get Node:** + +```go + url, err := url.JoinPath(registrarURL, "nodes", fmt.Sprint(nodeID)) + if err != nil { + panic(err) + } + + resp, err := http.DefaultClient.Get(url) + if err != nil { + panic(err) + } + + if resp.StatusCode != http.StatusOK { + panic(err) + } + defer resp.Body.Close() + + var result map[string]any + err = json.NewDecoder(resp.Body).Decode(&result) + if err != nil { + panic(err) + } + + fmt.Println(result) +``` diff --git a/node-registrar/main_.old b/node-registrar/main_.old deleted file mode 100644 index 2418d00..0000000 --- a/node-registrar/main_.old +++ /dev/null @@ -1,331 +0,0 @@ -package main - -import ( - "bytes" - "crypto/ed25519" - "crypto/rand" - "encoding/base64" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "net/http" - "strings" - "time" -) - -// Example for creating an account -func createAccountExample() (ed25519.PrivateKey, uint64, error) { - // Generate key pair - publicKey, privateKey, _ := ed25519.GenerateKey(nil) - - // Prepare request payload - timestamp := time.Now().Unix() - challenge := []byte(fmt.Sprintf("%d:%s", timestamp, base64.StdEncoding.EncodeToString(publicKey))) - signature := ed25519.Sign(privateKey, challenge) - - reqBody := fmt.Sprintf(`{ - "timestamp": %d, - "public_key": "%s", - "signature": "%s" - }`, - timestamp, - base64.StdEncoding.EncodeToString(publicKey), - base64.StdEncoding.EncodeToString(signature), - ) - - // Send request - resp, err := http.Post( - "http://localhost:8080/v1/accounts", - "application/json", - strings.NewReader(reqBody), - ) - if err != nil { - fmt.Println("Error sending request:", err) - return nil, 0, err - } - var twinID uint64 - if resp != nil { - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - fmt.Println("Error reading response body:", err) - } else { - fmt.Println("Response received:", string(body)) - var account map[string]interface{} - err = json.Unmarshal(body, &account) - if err != nil { - fmt.Println("Error unmarshalling response body:", err) - return nil, 0, err - } - twinID = uint64(account["TwinID"].(float64)) - } - } else { - fmt.Println("No response received") - } - - fmt.Println("TwinID:", twinID) - return privateKey, twinID, nil -} - -// Example for authenticating to access protected endpoint (e.g., update account) -func authenticatedRequestExample(twinID uint64, privateKey ed25519.PrivateKey) { - client := &http.Client{} - - // Create authentication challenge - timestamp := time.Now().Unix() - challenge := []byte(fmt.Sprintf("%d:%v", timestamp, twinID)) - signature := ed25519.Sign(privateKey, challenge) - - // Create request - req, _ := http.NewRequest( - "PATCH", - fmt.Sprintf("http://localhost:8080/v1/accounts/%v", twinID), - strings.NewReader(`{"relays": ["relay.example.com"], "rmb_enc_key": "abc123"}`), - ) - - // Set auth header - authHeader := fmt.Sprintf( - "%s:%s", - base64.StdEncoding.EncodeToString(challenge), - base64.StdEncoding.EncodeToString(signature), - ) - req.Header.Set("X-Auth", authHeader) - req.Header.Set("Content-Type", "application/json") - - // Send request - resp, err := client.Do(req) - if err != nil { - fmt.Println("Error sending request:", err) - return - } - - if resp != nil { - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - fmt.Println("Error reading response body:", err) - } else { - fmt.Println("Response received:", string(body)) - } - } else { - fmt.Println("No response received") - } -} - -// Get account -func getAccountExample(twinID uint64) { - client := &http.Client{} - - // Create request - req, _ := http.NewRequest( - "GET", - fmt.Sprintf("http://localhost:8080/v1/accounts/%v", twinID), - strings.NewReader(""), - ) - - // Send request - resp, err := client.Do(req) - if err != nil { - fmt.Println("Error sending request:", err) - return - } - - if resp != nil { - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - fmt.Println("Error reading response body:", err) - } else { - fmt.Println("Response received:", string(body)) - } - } else { - fmt.Println("No response received") - } -} - -func createFarmExample(twinID uint64, privateKey ed25519.PrivateKey) (uint64, error) { - client := &http.Client{} - randString := func(length int) string { - const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - result := make([]byte, length) - rand.Read(result) - for i, b := range result { - result[i] = chars[b%byte(len(chars))] - } - return string(result) - } - // Prepare farm payload - farmData := struct { - Name string `json:"farm_name"` - TwinID uint64 `json:"twin_id"` - }{ - Name: randString(20), - TwinID: twinID, - } - - body, _ := json.Marshal(farmData) - - // Create auth headers - timestamp := time.Now().Unix() - challenge := []byte(fmt.Sprintf("%d:%v", timestamp, twinID)) - signature := ed25519.Sign(privateKey, challenge) - - req, _ := http.NewRequest("POST", "http://localhost:8080/v1/farms", bytes.NewReader(body)) - - // Set required headers - authHeader := fmt.Sprintf( - "%s:%s", - base64.StdEncoding.EncodeToString(challenge), - base64.StdEncoding.EncodeToString(signature), - ) - req.Header.Set("X-Auth", authHeader) - req.Header.Set("Content-Type", "application/json") - - resp, err := client.Do(req) - if err != nil { - fmt.Println("Error: ", err) - return 0, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusCreated { - body, _ := io.ReadAll(resp.Body) - fmt.Println("farm creation failed: ", string(body)) - return 0, err - } - - // Parse response - var result struct { - FarmID uint64 `json:"farm_id"` - } - if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { - fmt.Println("Error: ", err) - return 0, err - } - - return result.FarmID, nil -} - -func registerNodeExample(twinID uint64, farmID uint64, privateKey ed25519.PrivateKey) error { - client := &http.Client{} - - // Prepare node payload matching server's NodeRegistrationRequest - nodeData := struct { - TwinID uint64 `json:"twin_id"` - FarmID uint64 `json:"farm_id"` - Resources struct { - CRU uint64 `json:"cru"` - SRU uint64 `json:"sru"` - HRU uint64 `json:"hru"` - MRU uint64 `json:"mru"` - } `json:"resources"` - Location struct { - Country string `json:"country"` - City string `json:"city"` - Longitude string `json:"longitude"` - Latitude string `json:"latitude"` - } `json:"location"` - Interfaces []struct { - Name string `json:"name"` - Mac string `json:"mac"` - IPs string `json:"ips"` - } `json:"interfaces"` - SecureBoot bool `json:"secure_boot"` - Virtualized bool `json:"virtualized"` - SerialNumber string `json:"serial_number"` - }{ - TwinID: twinID, - FarmID: farmID, - Resources: struct { - CRU uint64 `json:"cru"` - SRU uint64 `json:"sru"` - HRU uint64 `json:"hru"` - MRU uint64 `json:"mru"` - }{CRU: 4, SRU: 512, HRU: 1024, MRU: 2048}, - Location: struct { - Country string `json:"country"` - City string `json:"city"` - Longitude string `json:"longitude"` - Latitude string `json:"latitude"` - }{ - Country: "US", - City: "NY", - Longitude: "-74.005974", - Latitude: "40.712776", - }, - Interfaces: []struct { - Name string `json:"name"` - Mac string `json:"mac"` - IPs string `json:"ips"` - }{ - { - Name: "eth0", - Mac: "00:11:22:33:44:55", - IPs: "192.168.1.2/24", - }, - }, - SecureBoot: true, - Virtualized: false, - SerialNumber: "NODE-1234-ABCD", - } - - body, _ := json.Marshal(nodeData) - - // Create auth headers - timestamp := time.Now().Unix() - message := fmt.Sprintf("%d:%v", timestamp, twinID) - signature := ed25519.Sign(privateKey, []byte(message)) - - req, _ := http.NewRequest("POST", "http://localhost:8080/v1/nodes", bytes.NewReader(body)) - - // Set required headers - req.Header.Set("X-Auth", fmt.Sprintf( - "%s:%s", - base64.StdEncoding.EncodeToString([]byte(message)), - base64.StdEncoding.EncodeToString(signature), - )) - req.Header.Set("Content-Type", "application/json") - - resp, err := client.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusCreated { - body, _ := io.ReadAll(resp.Body) - return fmt.Errorf("node registration failed: %s", string(body)) - } - var result struct { - NodeID uint64 `json:"node_id"` - } - if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { - fmt.Println("Error: ", err) - return err - } - fmt.Println(result.NodeID) - return nil -} - -func main() { - privateKey, twinID, err := createAccountExample() - if err != nil { - fmt.Println("Error sending request:", err) - return - } - authenticatedRequestExample(twinID, privateKey) - getAccountExample(twinID) - farmID, err := createFarmExample(twinID, privateKey) - if err != nil { - fmt.Println("Error creating farm:", err) - return - } - fmt.Println("farm: ", farmID) - err = registerNodeExample(twinID, farmID, privateKey) - if err != nil { - fmt.Println("Error registering node:", err) - return - } - -} diff --git a/node-registrar/tools/account/README.md b/node-registrar/tools/account/README.md new file mode 100644 index 0000000..5ffaceb --- /dev/null +++ b/node-registrar/tools/account/README.md @@ -0,0 +1,46 @@ +# New Account Tool + +## Overview +This tool is a command-line application for creating an account on a specified network on the tfgrid4 registrar using an Ed25519 key pair. +It generates a public-private key pair from a seed and sends a signed request to register an account. + +## Installation +1. Clone the repository: + ```sh + git clone https://github.com/threefoldtech/tfgrid4-sdk-go + ``` +2. Navigate to the project directory: + ```sh + cd node-registrar + ``` +3. Build the application: + ```sh + go build -o new-account main.go + ``` + +## Usage +Run the tool using the following command: +```sh +./new-account -seed -network +``` + +### Parameters +- `-seed` (optional): A 64-character hexadecimal string representing the private key seed. **If omitted**, a new random seed is generated. +- `-network` (required): Specifies the target network (`dev`, `qa`, `test`, `main`). + +### Example Usage +#### 1. Using an Existing Seed +```sh +./new-account -seed aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899 -network dev +``` + +#### 2. Generating a New Seed +```sh +./new-account -network qa +``` +Output: +``` +New Seed (Hex): abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890 +12345678 +``` +The `TwinID` (e.g., `12345678`) is returned upon successful account creation. diff --git a/node-registrar/tools/account/main.go b/node-registrar/tools/account/main.go new file mode 100644 index 0000000..68f83f6 --- /dev/null +++ b/node-registrar/tools/account/main.go @@ -0,0 +1,118 @@ +package main + +import ( + "bytes" + "crypto/ed25519" + "crypto/rand" + "encoding/base64" + "encoding/hex" + "encoding/json" + "flag" + "fmt" + "net/http" + "net/url" + "time" + + "github.com/rs/zerolog/log" +) + +var urls = map[string]string{ + "dev": "https://registrar.dev4.grid.tf/v1", + "qa": "https://registrar.qa4.grid.tf/v1", + "test": "https://registrar.test4.grid.tf/v1", + "main": "https://registrar.prod4.grid.tf/v1", +} + +func main() { + var seed string + var network string + + flag.StringVar(&seed, "seed", "", "seed") + flag.StringVar(&network, "network", "", "network (dev, qa, test, main)") + flag.Parse() + + u, ok := urls[network] + if !ok { + log.Fatal().Msgf("invalid network %s", network) + } + + var publicKey ed25519.PublicKey + var privateKey ed25519.PrivateKey + var err error + + if len(seed) == 0 { + 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) + } + + publicKey, privateKey, err = getKeyPair(seed) + if err != nil { + log.Fatal().Err(err).Send() + } + + twinID, err := createAccount(privateKey, publicKey, u) + if err != nil { + log.Fatal().Err(err).Send() + } + + fmt.Println(twinID) +} + +func createAccount(privateKey ed25519.PrivateKey, publicKey ed25519.PublicKey, u string) (twinID uint64, err error) { + url, err := url.JoinPath(u, "accounts") + if err != nil { + return + } + + timestamp := time.Now().Unix() + publicKeyBase64 := base64.StdEncoding.EncodeToString(publicKey) + + challenge := []byte(fmt.Sprintf("%d:%v", timestamp, publicKeyBase64)) + signature := ed25519.Sign(privateKey, challenge) + + data := map[string]any{ + "public_key": publicKey, + "signature": signature, + "timestamp": timestamp, + } + + var body bytes.Buffer + err = json.NewEncoder(&body).Encode(data) + if err != nil { + return + } + + resp, err := http.DefaultClient.Post(url, "application/json", &body) + if err != nil { + return + } + + if resp.StatusCode != http.StatusCreated { + return twinID, fmt.Errorf("account not created successfully") + } + + defer resp.Body.Close() + + result := struct { + TwinID uint64 `json:"twin_id"` + }{} + + err = json.NewDecoder(resp.Body).Decode(&result) + return result.TwinID, err +} + +func getKeyPair(seed string) (ed25519.PublicKey, ed25519.PrivateKey, error) { + privateKeyBytes, err := hex.DecodeString(seed) + if err != nil { + return nil, nil, err + } + + privateKey := ed25519.NewKeyFromSeed(privateKeyBytes) + publicKey := privateKey.Public().(ed25519.PublicKey) + return publicKey, privateKey, nil +} diff --git a/node-registrar/tools/farm/README.md b/node-registrar/tools/farm/README.md new file mode 100644 index 0000000..072c6d4 --- /dev/null +++ b/node-registrar/tools/farm/README.md @@ -0,0 +1,33 @@ +# New Farm Tool + +## Overview +This tool allows users to create a farm account on a specified network. + +## Installation +1. Clone the repository: + ```sh + git clone https://github.com/threefoldtech/tfgrid4-sdk-go + ``` +2. Navigate to the project directory: + ```sh + cd node-registrar + ``` +3. Build the application: + ```sh + go build -o registrar-tool main.go + ``` + +## Usage +```sh +./new-farm -seed -network -farm_name +``` + +### Parameters +- `-seed` (required): A hexadecimal string used as a private key seed. +- `-network` (required): Specifies the network (`dev`, `qa`, `test`, `main`). +- `-farm_name` (required): The name of the farm to create. + +### Example Usage +```sh +./new-farm -seed aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899 -network dev -farm_name MyFarm +``` diff --git a/node-registrar/tools/farm/main.go b/node-registrar/tools/farm/main.go new file mode 100644 index 0000000..15a1eab --- /dev/null +++ b/node-registrar/tools/farm/main.go @@ -0,0 +1,159 @@ +package main + +import ( + "bytes" + "crypto/ed25519" + "encoding/base64" + "encoding/hex" + "encoding/json" + "flag" + "fmt" + "net/http" + "net/url" + "time" + + "github.com/rs/zerolog/log" +) + +var urls = map[string]string{ + "dev": "https://registrar.dev4.grid.tf/v1", + "qa": "https://registrar.qa4.grid.tf/v1", + "test": "https://registrar.test4.grid.tf/v1", + "main": "https://registrar.prod4.grid.tf/v1", +} + +func main() { + var seed string + var network string + var name string + + flag.StringVar(&seed, "seed", "", "seed") + flag.StringVar(&network, "network", "", "network (dev, qa, test, main)") + flag.StringVar(&name, "farm_name", "", "farm name") + flag.Parse() + + u, ok := urls[network] + if !ok { + log.Fatal().Msgf("invalid network %s", network) + } + + publicKey, privateKey, err := getKeyPair(seed) + if err != nil { + log.Fatal().Err(err).Send() + } + + twinID, err := getAccount(publicKey, u) + if err != nil { + log.Fatal().Err(err).Send() + } + + result, err := createFarm(name, twinID, privateKey, u) + if err != nil { + log.Fatal().Err(err).Send() + } + + fmt.Println(result) +} + +func createFarm(name string, twinID uint64, privateKey ed25519.PrivateKey, u string) (farmID uint64, err error) { + url, err := url.JoinPath(u, "farms") + if err != nil { + return + } + data := map[string]any{ + "farm_name": name, + "twin_id": twinID, + "dedicated": false, + } + + var body bytes.Buffer + err = json.NewEncoder(&body).Encode(data) + if err != nil { + return + } + + req, err := http.NewRequest("POST", url, &body) + if err != nil { + return + } + + timestamp := time.Now().Unix() + challenge := []byte(fmt.Sprintf("%d:%v", timestamp, twinID)) + signature := ed25519.Sign(privateKey, challenge) + + authHeader := fmt.Sprintf( + "%s:%s", + base64.StdEncoding.EncodeToString(challenge), + base64.StdEncoding.EncodeToString(signature), + ) + req.Header.Set("X-Auth", authHeader) + req.Header.Set("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusCreated { + return farmID, fmt.Errorf("could not create a farm") + } + + result := struct { + FarmID uint64 `json:"farm_id"` + }{} + + if err = json.NewDecoder(resp.Body).Decode(&result); err != nil { + return + } + fmt.Println(result) + return result.FarmID, nil +} + +func getAccount(publicKey []byte, u string) (twinID uint64, err error) { + url, err := url.JoinPath(u, "accounts") + if err != nil { + return + } + + publicKeyBase64 := base64.StdEncoding.EncodeToString(publicKey) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return + } + + q := req.URL.Query() + q.Add("public_key", publicKeyBase64) + req.URL.RawQuery = q.Encode() + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return twinID, fmt.Errorf("status code not ok") + } + + result := struct { + TwinID uint64 `json:"twin_id"` + }{} + + err = json.NewDecoder(resp.Body).Decode(&result) + return result.TwinID, err +} + +func getKeyPair(seed string) (ed25519.PublicKey, ed25519.PrivateKey, error) { + privateKeyBytes, err := hex.DecodeString(seed) + if err != nil { + return nil, nil, err + } + + privateKey := ed25519.NewKeyFromSeed(privateKeyBytes) + + publicKey := privateKey.Public().(ed25519.PublicKey) + return publicKey, privateKey, nil +}