You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

362 lines
13 KiB

  1. // Copyright 2017-2018 DERO Project. All rights reserved.
  2. // Use of this source code in any form is governed by RESEARCH license.
  3. // license can be found in the LICENSE file.
  4. // GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8
  5. //
  6. //
  7. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
  8. // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  9. // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
  10. // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  11. // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  12. // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  13. // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  14. // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
  15. // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  16. package walletapi
  17. import "fmt"
  18. import "sync"
  19. import "github.com/arnaucode/derosuite/config"
  20. import "github.com/arnaucode/derosuite/crypto"
  21. import "github.com/arnaucode/derosuite/crypto/ringct"
  22. import "github.com/arnaucode/derosuite/globals"
  23. import "github.com/arnaucode/derosuite/walletapi/mnemonics"
  24. import "github.com/arnaucode/derosuite/address"
  25. import "github.com/arnaucode/derosuite/blockchain/inputmaturity"
  26. type _Keys struct {
  27. Spendkey_Secret crypto.Key
  28. Spendkey_Public crypto.Key
  29. Viewkey_Secret crypto.Key
  30. Viewkey_Public crypto.Key
  31. }
  32. type Account struct {
  33. Keys _Keys
  34. SeedLanguage string
  35. ViewOnly bool // is this viewonly wallet
  36. Index_Global uint64 // till where the indexes have been processed
  37. Height uint64
  38. Balance uint64 // total balance of account
  39. Balance_Locked uint64 // balance locked
  40. Outputs_Array []TX_Wallet_Data // all outputs found in the chain belonging to us, as found in chain
  41. // uint64 si the Index_Global which is the unique number
  42. Outputs_Index map[uint64]bool // all outputs which are ours for deduplication
  43. Outputs_Ready map[uint64]TX_Wallet_Data // these outputs are ready for consumption ( maturity needs to be checked)
  44. Keyimages_Ready map[crypto.Key]bool // keyimages which are ready to get consumed, // we monitor them to find which
  45. Outputs_Consumed map[crypto.Key]TX_Wallet_Data // the key is the keyimage
  46. sync.Mutex // syncronise modifications to this structure
  47. }
  48. // this structure is kept by wallet
  49. type TX_Wallet_Data struct {
  50. TXdata globals.TX_Output_Data // all the fields of output data
  51. WAmount uint64 // actual amount, in case of miner it is verbatim, for other cases it decrypted
  52. WKey ringct.CtKey // key which is used to later send this specific output
  53. WKimage crypto.Key // key image which gets consumed when this output is spent
  54. WSpent bool // whether this output has been spent
  55. }
  56. // generate keys from using random numbers
  57. func Generate_Keys_From_Random() (user *Account, err error) {
  58. user = &Account{}
  59. seed := crypto.RandomScalar()
  60. user.Keys = Generate_Keys_From_Seed(*seed)
  61. // initialize maps now
  62. user.Outputs_Index = map[uint64]bool{}
  63. user.Outputs_Ready = map[uint64]TX_Wallet_Data{}
  64. user.Outputs_Consumed = map[crypto.Key]TX_Wallet_Data{}
  65. user.Keyimages_Ready = map[crypto.Key]bool{}
  66. return
  67. }
  68. // generate keys from seed which is from the recovery words
  69. // or we feed in direct
  70. func Generate_Keys_From_Seed(Seed crypto.Key) (keys _Keys) {
  71. // setup main keys
  72. keys.Spendkey_Secret = Seed
  73. keys.Spendkey_Public = *(Seed.PublicKey())
  74. // view keys are generated from secret ( so as single recovery seed is enough )
  75. hash := crypto.Key(crypto.Keccak256(Seed[:]))
  76. crypto.ScReduce32(&hash)
  77. keys.Viewkey_Secret = hash
  78. keys.Viewkey_Public = *(keys.Viewkey_Secret.PublicKey())
  79. return
  80. }
  81. // generate user account using recovery seeds
  82. func Generate_Account_From_Recovery_Words(words string) (user *Account, err error) {
  83. user = &Account{}
  84. language, seed, err := mnemonics.Words_To_Key(words)
  85. if err != nil {
  86. return
  87. }
  88. user.SeedLanguage = language
  89. user.Keys = Generate_Keys_From_Seed(seed)
  90. // initialize maps now
  91. user.Outputs_Index = map[uint64]bool{}
  92. user.Outputs_Ready = map[uint64]TX_Wallet_Data{}
  93. user.Outputs_Consumed = map[crypto.Key]TX_Wallet_Data{}
  94. user.Keyimages_Ready = map[crypto.Key]bool{}
  95. return
  96. }
  97. func Generate_Account_From_Seed(Seed crypto.Key) (user *Account, err error) {
  98. user = &Account{}
  99. // TODO check whether the seed is invalid
  100. user.Keys = Generate_Keys_From_Seed(Seed)
  101. // initialize maps now
  102. user.Outputs_Index = map[uint64]bool{}
  103. user.Outputs_Ready = map[uint64]TX_Wallet_Data{}
  104. user.Outputs_Consumed = map[crypto.Key]TX_Wallet_Data{}
  105. user.Keyimages_Ready = map[crypto.Key]bool{}
  106. return
  107. }
  108. // generate keys for view only wallet
  109. func Generate_Account_View_Only(Publicspend crypto.Key, ViewSecret crypto.Key) (user *Account, err error) {
  110. user = &Account{}
  111. // TODO check whether seed is valid secret
  112. user.Keys.Spendkey_Public = Publicspend
  113. user.Keys.Viewkey_Secret = ViewSecret
  114. user.Keys.Viewkey_Public = *(ViewSecret.PublicKey())
  115. user.ViewOnly = true
  116. // initialize maps
  117. user.Outputs_Index = map[uint64]bool{}
  118. user.Outputs_Ready = map[uint64]TX_Wallet_Data{}
  119. user.Outputs_Consumed = map[crypto.Key]TX_Wallet_Data{}
  120. user.Keyimages_Ready = map[crypto.Key]bool{}
  121. return
  122. }
  123. // convert key to seed using language
  124. func (user *Account) GetSeed() (str string) {
  125. return mnemonics.Key_To_Words(user.Keys.Spendkey_Secret, user.SeedLanguage)
  126. }
  127. // view wallet key consists of public spendkey and private view key
  128. func (user *Account) GetViewWalletKey() (str string) {
  129. return fmt.Sprintf("%s%s", user.Keys.Spendkey_Public, user.Keys.Viewkey_Secret)
  130. }
  131. // convert a user account to address
  132. func (user *Account) GetAddress() (addr address.Address) {
  133. switch globals.Config.Name {
  134. case "testnet":
  135. addr.Network = config.Testnet.Public_Address_Prefix //choose dETo
  136. default:
  137. fallthrough // assume mainnet
  138. case "mainnet":
  139. addr.Network = config.Mainnet.Public_Address_Prefix //choose dERo
  140. //panic(fmt.Sprintf("Unknown Network \"%s\"", globals.Config.Name))
  141. }
  142. addr.SpendKey = user.Keys.Spendkey_Public
  143. addr.ViewKey = user.Keys.Viewkey_Public
  144. return
  145. }
  146. // one simple function which does all the crypto to find out whether output belongs to this account
  147. // NOTE: this function only uses view key secret and Spendkey_Public
  148. // output index is the position of vout within the tx list itself
  149. func (user *Account) Is_Output_Ours(tx_public crypto.Key, output_index uint64, vout_key crypto.Key) bool {
  150. derivation := crypto.KeyDerivation(&tx_public, &user.Keys.Viewkey_Secret)
  151. derivation_public_key := derivation.KeyDerivation_To_PublicKey(output_index, user.Keys.Spendkey_Public)
  152. return derivation_public_key == vout_key
  153. }
  154. // this function does all the keyderivation required for decrypting ringct outputs, generate keyimage etc
  155. // also used when we build up a transaction for mining or sending amount
  156. func (user *Account) Generate_Helper_Key_Image(tx_public crypto.Key, output_index uint64) (ephermal_secret, ephermal_public, keyimage crypto.Key) {
  157. derivation := crypto.KeyDerivation(&tx_public, &user.Keys.Viewkey_Secret)
  158. ephermal_public = derivation.KeyDerivation_To_PublicKey(output_index, user.Keys.Spendkey_Public)
  159. ephermal_secret = derivation.KeyDerivation_To_PrivateKey(output_index, user.Keys.Spendkey_Secret)
  160. keyimage = crypto.GenerateKeyImage(ephermal_public, ephermal_secret)
  161. return
  162. }
  163. // this function decodes ringCT encoded output amounts
  164. // this is only possible if signature is full or simple
  165. func (user *Account) Decode_RingCT_Output(tx_public crypto.Key, output_index uint64, pkkey crypto.Key, tuple ringct.ECdhTuple, sigtype uint64) (amount uint64, mask ringct.Key, result bool) {
  166. derivation := crypto.KeyDerivation(&tx_public, &user.Keys.Viewkey_Secret)
  167. scalar_key := derivation.KeyDerivationToScalar(output_index)
  168. switch sigtype {
  169. case 0: // NOT possible , miner tx outputs are not hidden
  170. return
  171. case 1: // ringct MG // Both ringct outputs can be decoded using the same methods
  172. // however, original implementation has different methods, maybe need to evaluate more
  173. fallthrough
  174. case 2: // ringct sample
  175. amount, mask, result = ringct.Decode_Amount(tuple, ringct.Key(*scalar_key), ringct.Key(pkkey))
  176. default:
  177. return
  178. }
  179. return
  180. }
  181. // add the transaction to our wallet record, so as funds can be later on tracked
  182. // due to enhanced features, we have to wait and watch for all funds
  183. // this will extract secret keys from newly arrived funds to consume them later on
  184. func (user *Account) Add_Transaction_Record_Funds(txdata *globals.TX_Output_Data) (result bool) {
  185. user.Lock()
  186. defer user.Unlock()
  187. var tx_wallet TX_Wallet_Data
  188. // confirm once again that data belongs to this user
  189. if !user.Is_Output_Ours(txdata.Tx_Public_Key, txdata.Index_within_tx, crypto.Key(txdata.InKey.Destination)) {
  190. return false // output is not ours
  191. }
  192. // setup Amount
  193. switch txdata.SigType {
  194. case 0: // miner tx
  195. tx_wallet.WAmount = txdata.Amount
  196. tx_wallet.WKey.Mask = ringct.ZeroCommitment_From_Amount(txdata.Amount)
  197. case 1, 2: // ringct full/simple
  198. tx_wallet.WAmount, tx_wallet.WKey.Mask, result = user.Decode_RingCT_Output(txdata.Tx_Public_Key, txdata.Index_within_tx, crypto.Key(txdata.InKey.Mask), txdata.ECDHTuple,
  199. txdata.SigType)
  200. if result == false { // It's an internal error most probably
  201. return false
  202. }
  203. }
  204. tx_wallet.TXdata = *txdata
  205. // check whether we are deduplicating, is the transaction already in our records, skip it
  206. if _, ok := user.Outputs_Index[txdata.Index_Global]; ok { // transaction is already in our wallet, skip it for being duplicate
  207. return false
  208. }
  209. // if wallet is viewonly, we cannot track when the funds were spent
  210. // so lets skip the part, since we do not have th keys
  211. if !user.ViewOnly { // it's a full wallet, track spendable and get ready to spend
  212. secret_key, _, kimage := user.Generate_Helper_Key_Image(txdata.Tx_Public_Key, txdata.Index_within_tx)
  213. user.Keyimages_Ready[kimage] = true // monitor this key image for consumption
  214. tx_wallet.WKimage = kimage
  215. tx_wallet.WKey.Destination = ringct.Key(secret_key)
  216. }
  217. // add tx info to wallet
  218. user.Outputs_Index[txdata.Index_Global] = true // deduplication it if it ever comes again
  219. user.Outputs_Ready[txdata.Index_Global] = tx_wallet
  220. user.Outputs_Array = append(user.Outputs_Array, tx_wallet)
  221. return true
  222. }
  223. // check whether our fund is consumed
  224. // this is done by finding the keyimages floating in blockchain, to what keyimages belong to this account
  225. // if match is found, we have consumed our funds
  226. func (user *Account) Is_Our_Fund_Consumed(key_image crypto.Key) (amount uint64, result bool) {
  227. if _, ok := user.Keyimages_Ready[key_image]; ok {
  228. user.Lock()
  229. defer user.Unlock()
  230. for k, _ := range user.Outputs_Ready {
  231. if user.Outputs_Ready[k].WKimage == key_image {
  232. return user.Outputs_Ready[k].WAmount, true // return ammount and success
  233. }
  234. }
  235. fmt.Printf("This case should NOT be possible theoritically\n")
  236. return 0, true
  237. }
  238. return 0, false
  239. }
  240. // add the transaction to record,
  241. // this will mark the funds as consumed on the basis of keyimages
  242. // locate the transaction and get the amount , this is O(n), so we can tell how much funds were spent
  243. // cryptnote only allows to spend complete funds, change comes back
  244. func (user *Account) Consume_Transaction_Record_Funds(txdata *globals.TX_Output_Data, key_image crypto.Key) bool {
  245. var tx_wallet TX_Wallet_Data
  246. if _, ok := user.Keyimages_Ready[key_image]; ok {
  247. user.Lock()
  248. defer user.Unlock()
  249. for k, _ := range user.Outputs_Ready {
  250. if user.Outputs_Ready[k].WKimage == key_image { // find the input corressponding to this image
  251. // mark output as consumed, move it to consumed map, delete it from ready map
  252. tx_wallet.TXdata = *txdata
  253. tx_wallet.WAmount = user.Outputs_Ready[k].WAmount // take amount from original TX
  254. tx_wallet.WSpent = true // mark this fund as spent
  255. delete(user.Outputs_Ready, k)
  256. user.Outputs_Consumed[key_image] = tx_wallet
  257. user.Outputs_Array = append(user.Outputs_Array, user.Outputs_Consumed[key_image])
  258. return true // return success
  259. }
  260. }
  261. fmt.Printf("This case should NOT be possible theoritically\n")
  262. // locate the transaction and get the amount , this is O(n)
  263. return true
  264. }
  265. return false
  266. }
  267. // get the unlocked balance ( amounts which are mature and can be spent at this time )
  268. // offline wallets may get this wrong, since they may not have latest data
  269. // TODO: for offline wallets, we must make all balance as mature
  270. func (user *Account) Get_Balance() (mature_balance uint64, locked_balance uint64) {
  271. user.Lock()
  272. defer user.Unlock()
  273. for k := range user.Outputs_Ready {
  274. if inputmaturity.Is_Input_Mature(user.Height,
  275. user.Outputs_Ready[k].TXdata.Height,
  276. user.Outputs_Ready[k].TXdata.Unlock_Height,
  277. user.Outputs_Ready[k].TXdata.SigType) {
  278. mature_balance += user.Outputs_Ready[k].WAmount
  279. } else {
  280. locked_balance += user.Outputs_Ready[k].WAmount
  281. }
  282. }
  283. return
  284. }