|
|
// Copyright 2017-2018 DERO Project. All rights reserved.
// Use of this source code in any form is governed by RESEARCH license.
// license can be found in the LICENSE file.
// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8
//
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package walletapi
import "fmt" import "sync"
import "github.com/arnaucode/derosuite/config" import "github.com/arnaucode/derosuite/crypto" import "github.com/arnaucode/derosuite/crypto/ringct" import "github.com/arnaucode/derosuite/globals" import "github.com/arnaucode/derosuite/walletapi/mnemonics" import "github.com/arnaucode/derosuite/address" import "github.com/arnaucode/derosuite/blockchain/inputmaturity"
type _Keys struct { Spendkey_Secret crypto.Key Spendkey_Public crypto.Key Viewkey_Secret crypto.Key Viewkey_Public crypto.Key }
type Account struct { Keys _Keys SeedLanguage string
ViewOnly bool // is this viewonly wallet
Index_Global uint64 // till where the indexes have been processed
Height uint64
Balance uint64 // total balance of account
Balance_Locked uint64 // balance locked
Outputs_Array []TX_Wallet_Data // all outputs found in the chain belonging to us, as found in chain
// uint64 si the Index_Global which is the unique number
Outputs_Index map[uint64]bool // all outputs which are ours for deduplication
Outputs_Ready map[uint64]TX_Wallet_Data // these outputs are ready for consumption ( maturity needs to be checked)
Keyimages_Ready map[crypto.Key]bool // keyimages which are ready to get consumed, // we monitor them to find which
Outputs_Consumed map[crypto.Key]TX_Wallet_Data // the key is the keyimage
sync.Mutex // syncronise modifications to this structure
}
// this structure is kept by wallet
type TX_Wallet_Data struct { TXdata globals.TX_Output_Data // all the fields of output data
WAmount uint64 // actual amount, in case of miner it is verbatim, for other cases it decrypted
WKey ringct.CtKey // key which is used to later send this specific output
WKimage crypto.Key // key image which gets consumed when this output is spent
WSpent bool // whether this output has been spent
}
// generate keys from using random numbers
func Generate_Keys_From_Random() (user *Account, err error) { user = &Account{} seed := crypto.RandomScalar() user.Keys = Generate_Keys_From_Seed(*seed)
// initialize maps now
user.Outputs_Index = map[uint64]bool{} user.Outputs_Ready = map[uint64]TX_Wallet_Data{} user.Outputs_Consumed = map[crypto.Key]TX_Wallet_Data{} user.Keyimages_Ready = map[crypto.Key]bool{} return }
// generate keys from seed which is from the recovery words
// or we feed in direct
func Generate_Keys_From_Seed(Seed crypto.Key) (keys _Keys) {
// setup main keys
keys.Spendkey_Secret = Seed keys.Spendkey_Public = *(Seed.PublicKey())
// view keys are generated from secret ( so as single recovery seed is enough )
hash := crypto.Key(crypto.Keccak256(Seed[:])) crypto.ScReduce32(&hash) keys.Viewkey_Secret = hash keys.Viewkey_Public = *(keys.Viewkey_Secret.PublicKey())
return }
// generate user account using recovery seeds
func Generate_Account_From_Recovery_Words(words string) (user *Account, err error) { user = &Account{} language, seed, err := mnemonics.Words_To_Key(words) if err != nil { return }
user.SeedLanguage = language user.Keys = Generate_Keys_From_Seed(seed)
// initialize maps now
user.Outputs_Index = map[uint64]bool{} user.Outputs_Ready = map[uint64]TX_Wallet_Data{} user.Outputs_Consumed = map[crypto.Key]TX_Wallet_Data{} user.Keyimages_Ready = map[crypto.Key]bool{}
return }
func Generate_Account_From_Seed(Seed crypto.Key) (user *Account, err error) { user = &Account{}
// TODO check whether the seed is invalid
user.Keys = Generate_Keys_From_Seed(Seed)
// initialize maps now
user.Outputs_Index = map[uint64]bool{} user.Outputs_Ready = map[uint64]TX_Wallet_Data{} user.Outputs_Consumed = map[crypto.Key]TX_Wallet_Data{} user.Keyimages_Ready = map[crypto.Key]bool{}
return }
// generate keys for view only wallet
func Generate_Account_View_Only(Publicspend crypto.Key, ViewSecret crypto.Key) (user *Account, err error) {
user = &Account{}
// TODO check whether seed is valid secret
user.Keys.Spendkey_Public = Publicspend user.Keys.Viewkey_Secret = ViewSecret user.Keys.Viewkey_Public = *(ViewSecret.PublicKey()) user.ViewOnly = true
// initialize maps
user.Outputs_Index = map[uint64]bool{} user.Outputs_Ready = map[uint64]TX_Wallet_Data{} user.Outputs_Consumed = map[crypto.Key]TX_Wallet_Data{} user.Keyimages_Ready = map[crypto.Key]bool{}
return }
// convert key to seed using language
func (user *Account) GetSeed() (str string) { return mnemonics.Key_To_Words(user.Keys.Spendkey_Secret, user.SeedLanguage) }
// view wallet key consists of public spendkey and private view key
func (user *Account) GetViewWalletKey() (str string) { return fmt.Sprintf("%s%s", user.Keys.Spendkey_Public, user.Keys.Viewkey_Secret) }
// convert a user account to address
func (user *Account) GetAddress() (addr address.Address) { switch globals.Config.Name { case "testnet": addr.Network = config.Testnet.Public_Address_Prefix //choose dETo
default: fallthrough // assume mainnet
case "mainnet": addr.Network = config.Mainnet.Public_Address_Prefix //choose dERo
//panic(fmt.Sprintf("Unknown Network \"%s\"", globals.Config.Name))
}
addr.SpendKey = user.Keys.Spendkey_Public addr.ViewKey = user.Keys.Viewkey_Public
return }
// one simple function which does all the crypto to find out whether output belongs to this account
// NOTE: this function only uses view key secret and Spendkey_Public
// output index is the position of vout within the tx list itself
func (user *Account) Is_Output_Ours(tx_public crypto.Key, output_index uint64, vout_key crypto.Key) bool { derivation := crypto.KeyDerivation(&tx_public, &user.Keys.Viewkey_Secret) derivation_public_key := derivation.KeyDerivation_To_PublicKey(output_index, user.Keys.Spendkey_Public)
return derivation_public_key == vout_key }
// this function does all the keyderivation required for decrypting ringct outputs, generate keyimage etc
// also used when we build up a transaction for mining or sending amount
func (user *Account) Generate_Helper_Key_Image(tx_public crypto.Key, output_index uint64) (ephermal_secret, ephermal_public, keyimage crypto.Key) { derivation := crypto.KeyDerivation(&tx_public, &user.Keys.Viewkey_Secret) ephermal_public = derivation.KeyDerivation_To_PublicKey(output_index, user.Keys.Spendkey_Public) ephermal_secret = derivation.KeyDerivation_To_PrivateKey(output_index, user.Keys.Spendkey_Secret)
keyimage = crypto.GenerateKeyImage(ephermal_public, ephermal_secret)
return }
// this function decodes ringCT encoded output amounts
// this is only possible if signature is full or simple
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) {
derivation := crypto.KeyDerivation(&tx_public, &user.Keys.Viewkey_Secret) scalar_key := derivation.KeyDerivationToScalar(output_index)
switch sigtype { case 0: // NOT possible , miner tx outputs are not hidden
return case 1: // ringct MG // Both ringct outputs can be decoded using the same methods
// however, original implementation has different methods, maybe need to evaluate more
fallthrough case 2: // ringct sample
amount, mask, result = ringct.Decode_Amount(tuple, ringct.Key(*scalar_key), ringct.Key(pkkey))
default: return }
return }
// add the transaction to our wallet record, so as funds can be later on tracked
// due to enhanced features, we have to wait and watch for all funds
// this will extract secret keys from newly arrived funds to consume them later on
func (user *Account) Add_Transaction_Record_Funds(txdata *globals.TX_Output_Data) (result bool) { user.Lock() defer user.Unlock()
var tx_wallet TX_Wallet_Data // confirm once again that data belongs to this user
if !user.Is_Output_Ours(txdata.Tx_Public_Key, txdata.Index_within_tx, crypto.Key(txdata.InKey.Destination)) { return false // output is not ours
}
// setup Amount
switch txdata.SigType { case 0: // miner tx
tx_wallet.WAmount = txdata.Amount tx_wallet.WKey.Mask = ringct.ZeroCommitment_From_Amount(txdata.Amount)
case 1, 2: // ringct full/simple
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, txdata.SigType)
if result == false { // It's an internal error most probably
return false } }
tx_wallet.TXdata = *txdata
// check whether we are deduplicating, is the transaction already in our records, skip it
if _, ok := user.Outputs_Index[txdata.Index_Global]; ok { // transaction is already in our wallet, skip it for being duplicate
return false }
// if wallet is viewonly, we cannot track when the funds were spent
// so lets skip the part, since we do not have th keys
if !user.ViewOnly { // it's a full wallet, track spendable and get ready to spend
secret_key, _, kimage := user.Generate_Helper_Key_Image(txdata.Tx_Public_Key, txdata.Index_within_tx) user.Keyimages_Ready[kimage] = true // monitor this key image for consumption
tx_wallet.WKimage = kimage tx_wallet.WKey.Destination = ringct.Key(secret_key) }
// add tx info to wallet
user.Outputs_Index[txdata.Index_Global] = true // deduplication it if it ever comes again
user.Outputs_Ready[txdata.Index_Global] = tx_wallet user.Outputs_Array = append(user.Outputs_Array, tx_wallet)
return true }
// check whether our fund is consumed
// this is done by finding the keyimages floating in blockchain, to what keyimages belong to this account
// if match is found, we have consumed our funds
func (user *Account) Is_Our_Fund_Consumed(key_image crypto.Key) (amount uint64, result bool) { if _, ok := user.Keyimages_Ready[key_image]; ok { user.Lock() defer user.Unlock()
for k, _ := range user.Outputs_Ready { if user.Outputs_Ready[k].WKimage == key_image { return user.Outputs_Ready[k].WAmount, true // return ammount and success
} }
fmt.Printf("This case should NOT be possible theoritically\n") return 0, true } return 0, false }
// add the transaction to record,
// this will mark the funds as consumed on the basis of keyimages
// locate the transaction and get the amount , this is O(n), so we can tell how much funds were spent
// cryptnote only allows to spend complete funds, change comes back
func (user *Account) Consume_Transaction_Record_Funds(txdata *globals.TX_Output_Data, key_image crypto.Key) bool { var tx_wallet TX_Wallet_Data if _, ok := user.Keyimages_Ready[key_image]; ok { user.Lock() defer user.Unlock()
for k, _ := range user.Outputs_Ready { if user.Outputs_Ready[k].WKimage == key_image { // find the input corressponding to this image
// mark output as consumed, move it to consumed map, delete it from ready map
tx_wallet.TXdata = *txdata tx_wallet.WAmount = user.Outputs_Ready[k].WAmount // take amount from original TX
tx_wallet.WSpent = true // mark this fund as spent
delete(user.Outputs_Ready, k)
user.Outputs_Consumed[key_image] = tx_wallet user.Outputs_Array = append(user.Outputs_Array, user.Outputs_Consumed[key_image])
return true // return success
} }
fmt.Printf("This case should NOT be possible theoritically\n") // locate the transaction and get the amount , this is O(n)
return true }
return false }
// get the unlocked balance ( amounts which are mature and can be spent at this time )
// offline wallets may get this wrong, since they may not have latest data
// TODO: for offline wallets, we must make all balance as mature
func (user *Account) Get_Balance() (mature_balance uint64, locked_balance uint64) { user.Lock() defer user.Unlock()
for k := range user.Outputs_Ready {
if inputmaturity.Is_Input_Mature(user.Height, user.Outputs_Ready[k].TXdata.Height, user.Outputs_Ready[k].TXdata.Unlock_Height, user.Outputs_Ready[k].TXdata.SigType) { mature_balance += user.Outputs_Ready[k].WAmount } else { locked_balance += user.Outputs_Ready[k].WAmount } }
return }
|