config.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. package config
  2. import (
  3. "crypto/aes"
  4. "crypto/cipher"
  5. "crypto/rand"
  6. "crypto/sha256"
  7. "encoding/hex"
  8. "encoding/json"
  9. "errors"
  10. "fmt"
  11. "io"
  12. "io/ioutil"
  13. "os/user"
  14. "strings"
  15. "github.com/ditcraft/client/helpers"
  16. "github.com/ethereum/go-ethereum/crypto"
  17. "golang.org/x/crypto/ssh/terminal"
  18. )
  19. // DitConfig is exported since it needs to be accessed from other packages all the time
  20. var DitConfig ditConfig
  21. type ditConfig struct {
  22. DitCoordinator string `json:"dit_coordinator"`
  23. KNWVoting string `json:"knw_voting"`
  24. KNWToken string `json:"knw_token"`
  25. DitToken string `json:"dit_token,omitempty"`
  26. Currency string `json:"currency"`
  27. PassedKYC bool `json:"passed_kyc"`
  28. DemoModeActive bool `json:"demo_mode_active"`
  29. EthereumKeys ethereumKeys `json:"ethereum_keys"`
  30. LiveRepositories []Repository `json:"live_repositories"`
  31. DemoRepositories []Repository `json:"demo_repositories"`
  32. }
  33. type ethereumKeys struct {
  34. PrivateKey string `json:"private_key"`
  35. Address string `json:"address"`
  36. }
  37. // Repository struct, exported since its used in the ethereum package for new repositories
  38. type Repository struct {
  39. Name string `json:"name"`
  40. Provider string `json:"provider"`
  41. KnowledgeLabels []string `json:"knowledge_labels"`
  42. ActiveVotes []ActiveVote `json:"active_votes"`
  43. }
  44. // ActiveVote struct, exported since its used in the ethereum package for new votes
  45. type ActiveVote struct {
  46. ID int `json:"id"`
  47. KNWVoteID int `json:"knw_vote_id"`
  48. KnowledgeLabel string `json:"knowledge_label"`
  49. Choice int `json:"choice"`
  50. Salt int `json:"salt"`
  51. NumTokens int `json:"num_tokens"`
  52. NumVotes int `json:"num_votes"`
  53. NumKNW int `json:"num_knw"`
  54. CommitEnd int `json:"commit_end"`
  55. RevealEnd int `json:"reveal_end"`
  56. Resolved bool `json:"resolved"`
  57. DemoChoices []int `json:"demo_choices"`
  58. DemoSalts []int `json:"demo_salts"`
  59. }
  60. // GetPrivateKey will prompt the user for his password and return the decrypted ethereum private key
  61. func GetPrivateKey() (string, error) {
  62. // Prompting the user
  63. helpers.PrintLine("This action requires to send a transaction to the ethereum blockchain.", 0)
  64. helpers.Printf("Please provide your password to unlock your ethereum account: ", 0)
  65. password, err := terminal.ReadPassword(0)
  66. fmt.Printf("\n")
  67. if err != nil {
  68. return "", errors.New("Failed to retrieve password")
  69. }
  70. // Converting the encrypted private key from hex to bytes
  71. encPrivateKey, err := hex.DecodeString(DitConfig.EthereumKeys.PrivateKey)
  72. if err != nil {
  73. return "", errors.New("Failed to decode private key from config")
  74. }
  75. // Decrypting the private key
  76. decryptedPrivateKey, err := decrypt(encPrivateKey, string(password))
  77. if err != nil {
  78. return "", errors.New("Failed to decrypt the encrypted private key - wrong password?")
  79. }
  80. return string(decryptedPrivateKey), nil
  81. }
  82. // Load will load the config and set it to the exported variable "DitConfig"
  83. func Load() error {
  84. // Retrieve the home directory of the user
  85. usr, err := user.Current()
  86. if err != nil {
  87. return errors.New("Failed to retrieve home-directory of user")
  88. }
  89. // Reading the config file
  90. configFile, err := ioutil.ReadFile(usr.HomeDir + "/.ditconfig")
  91. if err != nil {
  92. if strings.Contains(err.Error(), "no such file or directory") {
  93. return errors.New("Config file not found - please use '" + helpers.ColorizeCommand("setup") + "'")
  94. }
  95. return errors.New("Failed to load config file")
  96. }
  97. // Parsing the json into a public object
  98. err = json.Unmarshal(configFile, &DitConfig)
  99. if err != nil {
  100. return errors.New("Failed to unmarshal JSON of config file")
  101. }
  102. // If the config is valid, it will contain an ethereum address with a length of 42
  103. if len(DitConfig.EthereumKeys.Address) != 42 {
  104. return errors.New("Invalid config file")
  105. }
  106. return nil
  107. }
  108. // Create will create a new config file
  109. func Create(_demoMode bool) error {
  110. helpers.PrintLine("Initializing the ditClient...", 0)
  111. DitConfig = ditConfig{}
  112. DitConfig.DemoModeActive = _demoMode
  113. if !DitConfig.DemoModeActive {
  114. helpers.PrintLine("You are initializing the client in live mode, you will be staking real xDai.", 1)
  115. helpers.PrintLine("If you just want to play around with dit, you can also use the demo mode with '"+helpers.ColorizeCommand("setup --demo")+"'", 0)
  116. fmt.Println()
  117. } else {
  118. helpers.PrintLine("You are initializing the client in demo mode, feel free to play around with it!", 0)
  119. helpers.PrintLine("If you want to switch to the live mode later on, you can do so with '"+helpers.ColorizeCommand("mode live")+"'", 0)
  120. fmt.Println()
  121. }
  122. // Prompting the user for his choice on the ethereum key generation/importing
  123. answerPrivateKeySelection := helpers.GetUserInputChoice("You can either (a) sample a new ethereum private-key or (b) provide your own one", "a", "b")
  124. // Sample new ethereum Keys
  125. if answerPrivateKeySelection == "a" {
  126. address, privateKey, err := sampleEthereumKeys()
  127. if err != nil {
  128. return err
  129. }
  130. DitConfig.EthereumKeys.PrivateKey = privateKey
  131. DitConfig.EthereumKeys.Address = address
  132. } else {
  133. // Import existing ones, prompting the user for input
  134. answerPrivateKeyInput := helpers.GetUserInput("Please provide a hex-formatted ethereum private-key")
  135. if len(answerPrivateKeyInput) == 64 || len(answerPrivateKeyInput) == 66 {
  136. // Remove possible "0x" at the beginning
  137. if strings.Contains(answerPrivateKeyInput, "0x") && len(answerPrivateKeyInput) == 66 {
  138. answerPrivateKeyInput = answerPrivateKeyInput[2:]
  139. }
  140. // Import the ethereum private key
  141. address, privateKey, err := importEthereumKey(answerPrivateKeyInput)
  142. if err != nil {
  143. return err
  144. }
  145. DitConfig.EthereumKeys.PrivateKey = privateKey
  146. DitConfig.EthereumKeys.Address = address
  147. } else {
  148. return errors.New("Invalid ethereum private-key")
  149. }
  150. }
  151. // Prompting the user to set a password for the private keys encryption
  152. var password []byte
  153. keepAsking := true
  154. for keepAsking {
  155. helpers.Printf("Please provide a password to encrypt your private key: ", 0)
  156. var err error
  157. password, err = terminal.ReadPassword(0)
  158. fmt.Printf("\n")
  159. if err != nil {
  160. return errors.New("Failed to retrieve password")
  161. }
  162. // Repeating the password to make sure that there are no typos
  163. helpers.Printf("Please repeat your password: ", 0)
  164. passwordAgain, err := terminal.ReadPassword(0)
  165. fmt.Printf("\n")
  166. if err != nil {
  167. return errors.New("Failed to retrieve password")
  168. }
  169. // If passwords don't match or are empty
  170. if string(passwordAgain) != string(password) {
  171. helpers.PrintLine("Passwords didn't match - try again!", 1)
  172. } else if len(password) == 0 {
  173. helpers.PrintLine("Password can't be empty - try again!", 1)
  174. } else {
  175. // Stop if nothing of the above is true
  176. keepAsking = false
  177. }
  178. }
  179. // Encrypt the private keys with the password
  180. encryptedPrivateKey, err := encrypt([]byte(DitConfig.EthereumKeys.PrivateKey), string(password))
  181. if err != nil {
  182. return errors.New("Failed to encrypt ethereum private-key")
  183. }
  184. DitConfig.EthereumKeys.PrivateKey = hex.EncodeToString(encryptedPrivateKey)
  185. DitConfig.LiveRepositories = make([]Repository, 0)
  186. DitConfig.DemoRepositories = make([]Repository, 0)
  187. // Write the config to the file
  188. err = Save()
  189. if err != nil {
  190. return err
  191. }
  192. helpers.PrintLine("Initialization successfull", 0)
  193. helpers.PrintLine("Your Ethereum Address is: "+DitConfig.EthereumKeys.Address, 0)
  194. return nil
  195. }
  196. // Save will write the current config object to the file
  197. func Save() error {
  198. // Convert the config object to JSON
  199. jsonBytes, err := json.Marshal(DitConfig)
  200. if err != nil {
  201. return errors.New("Failed to marshal JSON of config file")
  202. }
  203. // Retrieve the home directory of the user
  204. usr, err := user.Current()
  205. if err != nil {
  206. return errors.New("Failed to retrieve home-directory of user")
  207. }
  208. // Write the above into the config file
  209. err = ioutil.WriteFile(usr.HomeDir+"/.ditconfig", jsonBytes, 0644)
  210. if err != nil {
  211. return errors.New("Failed to write config file")
  212. }
  213. return nil
  214. }
  215. // importEthereumKey will return the private key and the address of an imported private key
  216. func importEthereumKey(privateKey string) (string, string, error) {
  217. helpers.PrintLine("Importing ethereum key...", 0)
  218. // Converting the private key string into a private key object
  219. key, err := crypto.HexToECDSA(privateKey)
  220. if err != nil {
  221. return "", "", errors.New("Failed to import ethereum keys")
  222. }
  223. // Calculating the address based on the privat key object
  224. address := crypto.PubkeyToAddress(key.PublicKey).Hex()
  225. // Converting the private key to string
  226. privateKey = hex.EncodeToString(key.D.Bytes())
  227. return address, privateKey, err
  228. }
  229. func sampleEthereumKeys() (string, string, error) {
  230. helpers.PrintLine("Sampling ethereum key...", 0)
  231. // Sampling a new private key
  232. key, err := crypto.GenerateKey()
  233. if err != nil {
  234. return "", "", errors.New("Failed to generate ethereum keys")
  235. }
  236. // Calculating the address based on the privat key object
  237. address := crypto.PubkeyToAddress(key.PublicKey).Hex()
  238. // Converting the private key to string
  239. privateKey := hex.EncodeToString(key.D.Bytes())
  240. return address, privateKey, err
  241. }
  242. // from: https://www.thepolyglotdeveloper.com/2018/02/encrypt-decrypt-data-golang-application-crypto-packages/
  243. func encrypt(data []byte, passphrase string) ([]byte, error) {
  244. block, _ := aes.NewCipher([]byte(createHash(passphrase)))
  245. gcm, err := cipher.NewGCM(block)
  246. if err != nil {
  247. return nil, err
  248. }
  249. nonce := make([]byte, gcm.NonceSize())
  250. if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
  251. return nil, err
  252. }
  253. ciphertext := gcm.Seal(nonce, nonce, data, nil)
  254. return ciphertext, nil
  255. }
  256. // from: https://www.thepolyglotdeveloper.com/2018/02/encrypt-decrypt-data-golang-application-crypto-packages/
  257. func decrypt(data []byte, passphrase string) ([]byte, error) {
  258. key := []byte(createHash(passphrase))
  259. block, err := aes.NewCipher(key)
  260. if err != nil {
  261. return nil, err
  262. }
  263. gcm, err := cipher.NewGCM(block)
  264. if err != nil {
  265. return nil, err
  266. }
  267. nonceSize := gcm.NonceSize()
  268. nonce, ciphertext := data[:nonceSize], data[nonceSize:]
  269. plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
  270. if err != nil {
  271. return nil, err
  272. }
  273. return plaintext, err
  274. }
  275. func createHash(key string) string {
  276. hasher := sha256.New()
  277. hasher.Write([]byte(key))
  278. return hex.EncodeToString(hasher.Sum(nil))[0:32]
  279. }