123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325 |
- package config
- import (
- "crypto/aes"
- "crypto/cipher"
- "crypto/rand"
- "crypto/sha256"
- "encoding/hex"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "os/user"
- "strings"
- "github.com/ditcraft/client/helpers"
- "github.com/ethereum/go-ethereum/crypto"
- "golang.org/x/crypto/ssh/terminal"
- )
- // DitConfig is exported since it needs to be accessed from other packages all the time
- var DitConfig ditConfig
- type ditConfig struct {
- DitCoordinator string `json:"dit_coordinator"`
- KNWVoting string `json:"knw_voting"`
- KNWToken string `json:"knw_token"`
- DitToken string `json:"dit_token,omitempty"`
- Currency string `json:"currency"`
- PassedKYC bool `json:"passed_kyc"`
- DemoModeActive bool `json:"demo_mode_active"`
- EthereumKeys ethereumKeys `json:"ethereum_keys"`
- LiveRepositories []Repository `json:"live_repositories"`
- DemoRepositories []Repository `json:"demo_repositories"`
- }
- type ethereumKeys struct {
- PrivateKey string `json:"private_key"`
- Address string `json:"address"`
- }
- // Repository struct, exported since its used in the ethereum package for new repositories
- type Repository struct {
- Name string `json:"name"`
- Provider string `json:"provider"`
- KnowledgeLabels []string `json:"knowledge_labels"`
- ActiveVotes []ActiveVote `json:"active_votes"`
- }
- // ActiveVote struct, exported since its used in the ethereum package for new votes
- type ActiveVote struct {
- ID int `json:"id"`
- KNWVoteID int `json:"knw_vote_id"`
- KnowledgeLabel string `json:"knowledge_label"`
- Choice int `json:"choice"`
- Salt int `json:"salt"`
- NumTokens int `json:"num_tokens"`
- NumVotes int `json:"num_votes"`
- NumKNW int `json:"num_knw"`
- CommitEnd int `json:"commit_end"`
- RevealEnd int `json:"reveal_end"`
- Resolved bool `json:"resolved"`
- DemoChoices []int `json:"demo_choices"`
- DemoSalts []int `json:"demo_salts"`
- }
- // GetPrivateKey will prompt the user for his password and return the decrypted ethereum private key
- func GetPrivateKey() (string, error) {
- // Prompting the user
- helpers.PrintLine("This action requires to send a transaction to the ethereum blockchain.", 0)
- helpers.Printf("Please provide your password to unlock your ethereum account: ", 0)
- password, err := terminal.ReadPassword(0)
- fmt.Printf("\n")
- if err != nil {
- return "", errors.New("Failed to retrieve password")
- }
- // Converting the encrypted private key from hex to bytes
- encPrivateKey, err := hex.DecodeString(DitConfig.EthereumKeys.PrivateKey)
- if err != nil {
- return "", errors.New("Failed to decode private key from config")
- }
- // Decrypting the private key
- decryptedPrivateKey, err := decrypt(encPrivateKey, string(password))
- if err != nil {
- return "", errors.New("Failed to decrypt the encrypted private key - wrong password?")
- }
- return string(decryptedPrivateKey), nil
- }
- // Load will load the config and set it to the exported variable "DitConfig"
- func Load() error {
- // Retrieve the home directory of the user
- usr, err := user.Current()
- if err != nil {
- return errors.New("Failed to retrieve home-directory of user")
- }
- // Reading the config file
- configFile, err := ioutil.ReadFile(usr.HomeDir + "/.ditconfig")
- if err != nil {
- if strings.Contains(err.Error(), "no such file or directory") {
- return errors.New("Config file not found - please use '" + helpers.ColorizeCommand("setup") + "'")
- }
- return errors.New("Failed to load config file")
- }
- // Parsing the json into a public object
- err = json.Unmarshal(configFile, &DitConfig)
- if err != nil {
- return errors.New("Failed to unmarshal JSON of config file")
- }
- // If the config is valid, it will contain an ethereum address with a length of 42
- if len(DitConfig.EthereumKeys.Address) != 42 {
- return errors.New("Invalid config file")
- }
- return nil
- }
- // Create will create a new config file
- func Create(_demoMode bool) error {
- helpers.PrintLine("Initializing the ditClient...", 0)
- DitConfig = ditConfig{}
- DitConfig.DemoModeActive = _demoMode
- if !DitConfig.DemoModeActive {
- helpers.PrintLine("You are initializing the client in live mode, you will be staking real xDai.", 1)
- helpers.PrintLine("If you just want to play around with dit, you can also use the demo mode with '"+helpers.ColorizeCommand("setup --demo")+"'", 0)
- fmt.Println()
- } else {
- helpers.PrintLine("You are initializing the client in demo mode, feel free to play around with it!", 0)
- helpers.PrintLine("If you want to switch to the live mode later on, you can do so with '"+helpers.ColorizeCommand("mode live")+"'", 0)
- fmt.Println()
- }
- // Prompting the user for his choice on the ethereum key generation/importing
- answerPrivateKeySelection := helpers.GetUserInputChoice("You can either (a) sample a new ethereum private-key or (b) provide your own one", "a", "b")
- // Sample new ethereum Keys
- if answerPrivateKeySelection == "a" {
- address, privateKey, err := sampleEthereumKeys()
- if err != nil {
- return err
- }
- DitConfig.EthereumKeys.PrivateKey = privateKey
- DitConfig.EthereumKeys.Address = address
- } else {
- // Import existing ones, prompting the user for input
- answerPrivateKeyInput := helpers.GetUserInput("Please provide a hex-formatted ethereum private-key")
- if len(answerPrivateKeyInput) == 64 || len(answerPrivateKeyInput) == 66 {
- // Remove possible "0x" at the beginning
- if strings.Contains(answerPrivateKeyInput, "0x") && len(answerPrivateKeyInput) == 66 {
- answerPrivateKeyInput = answerPrivateKeyInput[2:]
- }
- // Import the ethereum private key
- address, privateKey, err := importEthereumKey(answerPrivateKeyInput)
- if err != nil {
- return err
- }
- DitConfig.EthereumKeys.PrivateKey = privateKey
- DitConfig.EthereumKeys.Address = address
- } else {
- return errors.New("Invalid ethereum private-key")
- }
- }
- // Prompting the user to set a password for the private keys encryption
- var password []byte
- keepAsking := true
- for keepAsking {
- helpers.Printf("Please provide a password to encrypt your private key: ", 0)
- var err error
- password, err = terminal.ReadPassword(0)
- fmt.Printf("\n")
- if err != nil {
- return errors.New("Failed to retrieve password")
- }
- // Repeating the password to make sure that there are no typos
- helpers.Printf("Please repeat your password: ", 0)
- passwordAgain, err := terminal.ReadPassword(0)
- fmt.Printf("\n")
- if err != nil {
- return errors.New("Failed to retrieve password")
- }
- // If passwords don't match or are empty
- if string(passwordAgain) != string(password) {
- helpers.PrintLine("Passwords didn't match - try again!", 1)
- } else if len(password) == 0 {
- helpers.PrintLine("Password can't be empty - try again!", 1)
- } else {
- // Stop if nothing of the above is true
- keepAsking = false
- }
- }
- // Encrypt the private keys with the password
- encryptedPrivateKey, err := encrypt([]byte(DitConfig.EthereumKeys.PrivateKey), string(password))
- if err != nil {
- return errors.New("Failed to encrypt ethereum private-key")
- }
- DitConfig.EthereumKeys.PrivateKey = hex.EncodeToString(encryptedPrivateKey)
- DitConfig.LiveRepositories = make([]Repository, 0)
- DitConfig.DemoRepositories = make([]Repository, 0)
- // Write the config to the file
- err = Save()
- if err != nil {
- return err
- }
- helpers.PrintLine("Initialization successfull", 0)
- helpers.PrintLine("Your Ethereum Address is: "+DitConfig.EthereumKeys.Address, 0)
- return nil
- }
- // Save will write the current config object to the file
- func Save() error {
- // Convert the config object to JSON
- jsonBytes, err := json.Marshal(DitConfig)
- if err != nil {
- return errors.New("Failed to marshal JSON of config file")
- }
- // Retrieve the home directory of the user
- usr, err := user.Current()
- if err != nil {
- return errors.New("Failed to retrieve home-directory of user")
- }
- // Write the above into the config file
- err = ioutil.WriteFile(usr.HomeDir+"/.ditconfig", jsonBytes, 0644)
- if err != nil {
- return errors.New("Failed to write config file")
- }
- return nil
- }
- // importEthereumKey will return the private key and the address of an imported private key
- func importEthereumKey(privateKey string) (string, string, error) {
- helpers.PrintLine("Importing ethereum key...", 0)
- // Converting the private key string into a private key object
- key, err := crypto.HexToECDSA(privateKey)
- if err != nil {
- return "", "", errors.New("Failed to import ethereum keys")
- }
- // Calculating the address based on the privat key object
- address := crypto.PubkeyToAddress(key.PublicKey).Hex()
- // Converting the private key to string
- privateKey = hex.EncodeToString(key.D.Bytes())
- return address, privateKey, err
- }
- func sampleEthereumKeys() (string, string, error) {
- helpers.PrintLine("Sampling ethereum key...", 0)
- // Sampling a new private key
- key, err := crypto.GenerateKey()
- if err != nil {
- return "", "", errors.New("Failed to generate ethereum keys")
- }
- // Calculating the address based on the privat key object
- address := crypto.PubkeyToAddress(key.PublicKey).Hex()
- // Converting the private key to string
- privateKey := hex.EncodeToString(key.D.Bytes())
- return address, privateKey, err
- }
- // from: https://www.thepolyglotdeveloper.com/2018/02/encrypt-decrypt-data-golang-application-crypto-packages/
- func encrypt(data []byte, passphrase string) ([]byte, error) {
- block, _ := aes.NewCipher([]byte(createHash(passphrase)))
- gcm, err := cipher.NewGCM(block)
- if err != nil {
- return nil, err
- }
- nonce := make([]byte, gcm.NonceSize())
- if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
- return nil, err
- }
- ciphertext := gcm.Seal(nonce, nonce, data, nil)
- return ciphertext, nil
- }
- // from: https://www.thepolyglotdeveloper.com/2018/02/encrypt-decrypt-data-golang-application-crypto-packages/
- func decrypt(data []byte, passphrase string) ([]byte, error) {
- key := []byte(createHash(passphrase))
- block, err := aes.NewCipher(key)
- if err != nil {
- return nil, err
- }
- gcm, err := cipher.NewGCM(block)
- if err != nil {
- return nil, err
- }
- nonceSize := gcm.NonceSize()
- nonce, ciphertext := data[:nonceSize], data[nonceSize:]
- plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
- if err != nil {
- return nil, err
- }
- return plaintext, err
- }
- func createHash(key string) string {
- hasher := sha256.New()
- hasher.Write([]byte(key))
- return hex.EncodeToString(hasher.Sum(nil))[0:32]
- }
|