From fc0c819c437cd369b3cae3564c90c2e253676407 Mon Sep 17 00:00:00 2001 From: Ivan Kuznetsov Date: Sun, 10 Sep 2017 10:54:58 +0700 Subject: [PATCH] Extract some structs into separate files --- block.go | 52 +++---- blockchain.go | 333 +++++++++++++++++++---------------------- blockchain_iterator.go | 34 +++++ transaction.go | 122 +++++---------- transaction_input.go | 17 +++ transaction_output.go | 29 ++++ wallet.go | 100 +------------ wallets.go | 94 ++++++++++++ 8 files changed, 400 insertions(+), 381 deletions(-) create mode 100644 blockchain_iterator.go create mode 100644 transaction_input.go create mode 100644 transaction_output.go create mode 100644 wallets.go diff --git a/block.go b/block.go index 04d96cc..3baaa46 100644 --- a/block.go +++ b/block.go @@ -17,32 +17,6 @@ type Block struct { Nonce int } -// Serialize serializes the block -func (b *Block) Serialize() []byte { - var result bytes.Buffer - encoder := gob.NewEncoder(&result) - - err := encoder.Encode(b) - if err != nil { - log.Panic(err) - } - - return result.Bytes() -} - -// HashTransactions returns a hash of the transactions in the block -func (b *Block) HashTransactions() []byte { - var txHashes [][]byte - var txHash [32]byte - - for _, tx := range b.Transactions { - txHashes = append(txHashes, tx.ID) - } - txHash = sha256.Sum256(bytes.Join(txHashes, []byte{})) - - return txHash[:] -} - // NewBlock creates and returns Block func NewBlock(transactions []*Transaction, prevBlockHash []byte) *Block { block := &Block{time.Now().Unix(), transactions, prevBlockHash, []byte{}, 0} @@ -72,3 +46,29 @@ func DeserializeBlock(d []byte) *Block { return &block } + +// HashTransactions returns a hash of the transactions in the block +func (b *Block) HashTransactions() []byte { + var txHashes [][]byte + var txHash [32]byte + + for _, tx := range b.Transactions { + txHashes = append(txHashes, tx.ID) + } + txHash = sha256.Sum256(bytes.Join(txHashes, []byte{})) + + return txHash[:] +} + +// Serialize serializes the block +func (b *Block) Serialize() []byte { + var result bytes.Buffer + encoder := gob.NewEncoder(&result) + + err := encoder.Encode(b) + if err != nil { + log.Panic(err) + } + + return result.Bytes() +} diff --git a/blockchain.go b/blockchain.go index 48709ae..ef4d221 100644 --- a/blockchain.go +++ b/blockchain.go @@ -22,25 +22,38 @@ type Blockchain struct { db *bolt.DB } -// BlockchainIterator is used to iterate over blockchain blocks -type BlockchainIterator struct { - currentHash []byte - db *bolt.DB -} - -// MineBlock mines a new block with the provided transactions -func (bc *Blockchain) MineBlock(transactions []*Transaction) { - var lastHash []byte - - for _, tx := range transactions { - if bc.VerifyTransaction(tx) != true { - log.Panic("ERROR: Invalid transaction") - } +// CreateBlockchain creates a new blockchain DB +func CreateBlockchain(address string) *Blockchain { + if dbExists() { + fmt.Println("Blockchain already exists.") + os.Exit(1) } - err := bc.db.View(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte(blocksBucket)) - lastHash = b.Get([]byte("l")) + var tip []byte + db, err := bolt.Open(dbFile, 0600, nil) + if err != nil { + log.Panic(err) + } + + err = db.Update(func(tx *bolt.Tx) error { + cbtx := NewCoinbaseTX(address, genesisCoinbaseData) + genesis := NewGenesisBlock(cbtx) + + b, err := tx.CreateBucket([]byte(blocksBucket)) + if err != nil { + log.Panic(err) + } + + err = b.Put(genesis.Hash, genesis.Serialize()) + if err != nil { + log.Panic(err) + } + + err = b.Put([]byte("l"), genesis.Hash) + if err != nil { + log.Panic(err) + } + tip = genesis.Hash return nil }) @@ -49,24 +62,63 @@ func (bc *Blockchain) MineBlock(transactions []*Transaction) { log.Panic(err) } - newBlock := NewBlock(transactions, lastHash) + bc := Blockchain{tip, db} - err = bc.db.Update(func(tx *bolt.Tx) error { + return &bc +} + +// NewBlockchain creates a new Blockchain with genesis Block +func NewBlockchain(address string) *Blockchain { + if dbExists() == false { + fmt.Println("No existing blockchain found. Create one first.") + os.Exit(1) + } + + var tip []byte + db, err := bolt.Open(dbFile, 0600, nil) + if err != nil { + log.Panic(err) + } + + err = db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) - err := b.Put(newBlock.Hash, newBlock.Serialize()) - if err != nil { - log.Panic(err) - } - - err = b.Put([]byte("l"), newBlock.Hash) - if err != nil { - log.Panic(err) - } - - bc.tip = newBlock.Hash + tip = b.Get([]byte("l")) return nil }) + + if err != nil { + log.Panic(err) + } + + bc := Blockchain{tip, db} + + return &bc +} + +// FindSpendableOutputs finds and returns unspent outputs to reference in inputs +func (bc *Blockchain) FindSpendableOutputs(pubKeyHash []byte, amount int) (int, map[string][]int) { + unspentOutputs := make(map[string][]int) + unspentTXs := bc.FindUnspentTransactions(pubKeyHash) + accumulated := 0 + +Work: + for _, tx := range unspentTXs { + txID := hex.EncodeToString(tx.ID) + + for outIdx, out := range tx.Vout { + if out.Unlock(pubKeyHash) && accumulated < amount { + accumulated += out.Value + unspentOutputs[txID] = append(unspentOutputs[txID], outIdx) + + if accumulated >= amount { + break Work + } + } + } + } + + return accumulated, unspentOutputs } // FindTransaction finds a transaction by its ID @@ -90,42 +142,6 @@ func (bc *Blockchain) FindTransaction(ID []byte) (Transaction, error) { return Transaction{}, errors.New("Transaction is not found") } -// SignTransaction signs a Transaction -func (bc *Blockchain) SignTransaction(tx *Transaction, privKey ecdsa.PrivateKey) { - prevTXs := make(map[string]Transaction) - - for _, vin := range tx.Vin { - prevTX, err := bc.FindTransaction(vin.Txid) - if err != nil { - log.Panic(err) - } - prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX - } - - tx.Sign(privKey, prevTXs) -} - -// VerifyTransaction verifies transaction -func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool { - prevTXs := make(map[string]Transaction) - - for _, vin := range tx.Vin { - prevTX, err := bc.FindTransaction(vin.Txid) - if err != nil { - log.Panic(err) - } - prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX - } - - for _, tx := range prevTXs { - fmt.Println(tx) - } - // fmt.Println() - // fmt.Println(tx) - - return tx.Verify(prevTXs) -} - // FindUnspentTransactions returns a list of transactions containing unspent outputs func (bc *Blockchain) FindUnspentTransactions(pubKeyHash []byte) []Transaction { var unspentTXs []Transaction @@ -188,29 +204,81 @@ func (bc *Blockchain) FindUTXO(pubKeyHash []byte) []TXOutput { return UTXOs } -// FindSpendableOutputs finds and returns unspent outputs to reference in inputs -func (bc *Blockchain) FindSpendableOutputs(pubKeyHash []byte, amount int) (int, map[string][]int) { - unspentOutputs := make(map[string][]int) - unspentTXs := bc.FindUnspentTransactions(pubKeyHash) - accumulated := 0 +// MineBlock mines a new block with the provided transactions +func (bc *Blockchain) MineBlock(transactions []*Transaction) { + var lastHash []byte -Work: - for _, tx := range unspentTXs { - txID := hex.EncodeToString(tx.ID) - - for outIdx, out := range tx.Vout { - if out.Unlock(pubKeyHash) && accumulated < amount { - accumulated += out.Value - unspentOutputs[txID] = append(unspentOutputs[txID], outIdx) - - if accumulated >= amount { - break Work - } - } + for _, tx := range transactions { + if bc.VerifyTransaction(tx) != true { + log.Panic("ERROR: Invalid transaction") } } - return accumulated, unspentOutputs + err := bc.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(blocksBucket)) + lastHash = b.Get([]byte("l")) + + return nil + }) + + if err != nil { + log.Panic(err) + } + + newBlock := NewBlock(transactions, lastHash) + + err = bc.db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(blocksBucket)) + err := b.Put(newBlock.Hash, newBlock.Serialize()) + if err != nil { + log.Panic(err) + } + + err = b.Put([]byte("l"), newBlock.Hash) + if err != nil { + log.Panic(err) + } + + bc.tip = newBlock.Hash + + return nil + }) +} + +// SignTransaction signs a Transaction +func (bc *Blockchain) SignTransaction(tx *Transaction, privKey ecdsa.PrivateKey) { + prevTXs := make(map[string]Transaction) + + for _, vin := range tx.Vin { + prevTX, err := bc.FindTransaction(vin.Txid) + if err != nil { + log.Panic(err) + } + prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX + } + + tx.Sign(privKey, prevTXs) +} + +// VerifyTransaction verifies transaction +func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool { + prevTXs := make(map[string]Transaction) + + for _, vin := range tx.Vin { + prevTX, err := bc.FindTransaction(vin.Txid) + if err != nil { + log.Panic(err) + } + prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX + } + + for _, tx := range prevTXs { + fmt.Println(tx) + } + // fmt.Println() + // fmt.Println(tx) + + return tx.Verify(prevTXs) } // Iterator returns a BlockchainIterat @@ -220,27 +288,6 @@ func (bc *Blockchain) Iterator() *BlockchainIterator { return bci } -// Next returns next block starting from the tip -func (i *BlockchainIterator) Next() *Block { - var block *Block - - err := i.db.View(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte(blocksBucket)) - encodedBlock := b.Get(i.currentHash) - block = DeserializeBlock(encodedBlock) - - return nil - }) - - if err != nil { - log.Panic(err) - } - - i.currentHash = block.PrevBlockHash - - return block -} - func dbExists() bool { if _, err := os.Stat(dbFile); os.IsNotExist(err) { return false @@ -248,77 +295,3 @@ func dbExists() bool { return true } - -// NewBlockchain creates a new Blockchain with genesis Block -func NewBlockchain(address string) *Blockchain { - if dbExists() == false { - fmt.Println("No existing blockchain found. Create one first.") - os.Exit(1) - } - - var tip []byte - db, err := bolt.Open(dbFile, 0600, nil) - if err != nil { - log.Panic(err) - } - - err = db.Update(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte(blocksBucket)) - tip = b.Get([]byte("l")) - - return nil - }) - - if err != nil { - log.Panic(err) - } - - bc := Blockchain{tip, db} - - return &bc -} - -// CreateBlockchain creates a new blockchain DB -func CreateBlockchain(address string) *Blockchain { - if dbExists() { - fmt.Println("Blockchain already exists.") - os.Exit(1) - } - - var tip []byte - db, err := bolt.Open(dbFile, 0600, nil) - if err != nil { - log.Panic(err) - } - - err = db.Update(func(tx *bolt.Tx) error { - cbtx := NewCoinbaseTX(address, genesisCoinbaseData) - genesis := NewGenesisBlock(cbtx) - - b, err := tx.CreateBucket([]byte(blocksBucket)) - if err != nil { - log.Panic(err) - } - - err = b.Put(genesis.Hash, genesis.Serialize()) - if err != nil { - log.Panic(err) - } - - err = b.Put([]byte("l"), genesis.Hash) - if err != nil { - log.Panic(err) - } - tip = genesis.Hash - - return nil - }) - - if err != nil { - log.Panic(err) - } - - bc := Blockchain{tip, db} - - return &bc -} diff --git a/blockchain_iterator.go b/blockchain_iterator.go new file mode 100644 index 0000000..305311d --- /dev/null +++ b/blockchain_iterator.go @@ -0,0 +1,34 @@ +package main + +import ( + "log" + + "github.com/boltdb/bolt" +) + +// BlockchainIterator is used to iterate over blockchain blocks +type BlockchainIterator struct { + currentHash []byte + db *bolt.DB +} + +// Next returns next block starting from the tip +func (i *BlockchainIterator) Next() *Block { + var block *Block + + err := i.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(blocksBucket)) + encodedBlock := b.Get(i.currentHash) + block = DeserializeBlock(encodedBlock) + + return nil + }) + + if err != nil { + log.Panic(err) + } + + i.currentHash = block.PrevBlockHash + + return block +} diff --git a/transaction.go b/transaction.go index b7463f1..e5176d6 100644 --- a/transaction.go +++ b/transaction.go @@ -29,29 +29,6 @@ func (tx Transaction) IsCoinbase() bool { return len(tx.Vin) == 1 && len(tx.Vin[0].Txid) == 0 && tx.Vin[0].Vout == -1 } -// String returns a human-readable representation of a transaction -func (tx Transaction) String() string { - var lines []string - - lines = append(lines, fmt.Sprintf("Transaction %x:", tx.ID)) - - for i, input := range tx.Vin { - - lines = append(lines, fmt.Sprintf(" Input %d:", i)) - lines = append(lines, fmt.Sprintf(" TXID: %x", input.Txid)) - lines = append(lines, fmt.Sprintf(" Out: %d", input.Vout)) - lines = append(lines, fmt.Sprintf(" Script: %x", input.ScriptSig)) - } - - for i, output := range tx.Vout { - lines = append(lines, fmt.Sprintf(" Output %d:", i)) - lines = append(lines, fmt.Sprintf(" Value: %d", output.Value)) - lines = append(lines, fmt.Sprintf(" Script: %x", output.ScriptPubKey)) - } - - return strings.Join(lines, "\n") -} - // SetID sets ID of a transaction func (tx *Transaction) SetID() { var encoded bytes.Buffer @@ -66,24 +43,6 @@ func (tx *Transaction) SetID() { tx.ID = hash[:] } -// TrimmedCopy creates a trimmed copy of Transaction to be used in signing -func (tx *Transaction) TrimmedCopy() Transaction { - var inputs []TXInput - var outputs []TXOutput - - for _, vin := range tx.Vin { - inputs = append(inputs, TXInput{vin.Txid, vin.Vout, []byte{}}) - } - - for _, vout := range tx.Vout { - outputs = append(outputs, TXOutput{vout.Value, vout.ScriptPubKey}) - } - - txCopy := Transaction{tx.ID, inputs, outputs} - - return txCopy -} - // Sign signs each input of a Transaction func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transaction) { if tx.IsCoinbase() { @@ -114,6 +73,47 @@ func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transac } } +// String returns a human-readable representation of a transaction +func (tx Transaction) String() string { + var lines []string + + lines = append(lines, fmt.Sprintf("Transaction %x:", tx.ID)) + + for i, input := range tx.Vin { + + lines = append(lines, fmt.Sprintf(" Input %d:", i)) + lines = append(lines, fmt.Sprintf(" TXID: %x", input.Txid)) + lines = append(lines, fmt.Sprintf(" Out: %d", input.Vout)) + lines = append(lines, fmt.Sprintf(" Script: %x", input.ScriptSig)) + } + + for i, output := range tx.Vout { + lines = append(lines, fmt.Sprintf(" Output %d:", i)) + lines = append(lines, fmt.Sprintf(" Value: %d", output.Value)) + lines = append(lines, fmt.Sprintf(" Script: %x", output.ScriptPubKey)) + } + + return strings.Join(lines, "\n") +} + +// TrimmedCopy creates a trimmed copy of Transaction to be used in signing +func (tx *Transaction) TrimmedCopy() Transaction { + var inputs []TXInput + var outputs []TXOutput + + for _, vin := range tx.Vin { + inputs = append(inputs, TXInput{vin.Txid, vin.Vout, []byte{}}) + } + + for _, vout := range tx.Vout { + outputs = append(outputs, TXOutput{vout.Value, vout.ScriptPubKey}) + } + + txCopy := Transaction{tx.ID, inputs, outputs} + + return txCopy +} + // Verify verifies signatures of Transaction inputs func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { sigLen := 64 @@ -160,46 +160,6 @@ func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { return true } -// TXInput represents a transaction input -type TXInput struct { - Txid []byte - Vout int - ScriptSig []byte -} - -// TXOutput represents a transaction output -type TXOutput struct { - Value int - ScriptPubKey []byte -} - -// UnlocksOutputWith checks whether the address initiated the transaction -func (in *TXInput) UnlocksOutputWith(pubKeyHash []byte) bool { - lockingHash := HashPubKey(in.ScriptSig) - - return bytes.Compare(lockingHash, pubKeyHash) == 0 -} - -// Lock signs the output -func (out *TXOutput) Lock(address []byte) { - pubKeyHash := Base58Decode(address) - pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4] - out.ScriptPubKey = pubKeyHash -} - -// Unlock checks if the output can be used by the owner of the pubkey -func (out *TXOutput) Unlock(pubKeyHash []byte) bool { - return bytes.Compare(out.ScriptPubKey, pubKeyHash) == 0 -} - -// NewTXOutput create a new TXOutput -func NewTXOutput(value int, address string) *TXOutput { - txo := &TXOutput{value, nil} - txo.Lock([]byte(address)) - - return txo -} - // NewCoinbaseTX creates a new coinbase transaction func NewCoinbaseTX(to, data string) *Transaction { if data == "" { diff --git a/transaction_input.go b/transaction_input.go new file mode 100644 index 0000000..1c57c2b --- /dev/null +++ b/transaction_input.go @@ -0,0 +1,17 @@ +package main + +import "bytes" + +// TXInput represents a transaction input +type TXInput struct { + Txid []byte + Vout int + ScriptSig []byte +} + +// UnlocksOutputWith checks whether the address initiated the transaction +func (in *TXInput) UnlocksOutputWith(pubKeyHash []byte) bool { + lockingHash := HashPubKey(in.ScriptSig) + + return bytes.Compare(lockingHash, pubKeyHash) == 0 +} diff --git a/transaction_output.go b/transaction_output.go new file mode 100644 index 0000000..f509dd8 --- /dev/null +++ b/transaction_output.go @@ -0,0 +1,29 @@ +package main + +import "bytes" + +// TXOutput represents a transaction output +type TXOutput struct { + Value int + ScriptPubKey []byte +} + +// Lock signs the output +func (out *TXOutput) Lock(address []byte) { + pubKeyHash := Base58Decode(address) + pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4] + out.ScriptPubKey = pubKeyHash +} + +// Unlock checks if the output can be used by the owner of the pubkey +func (out *TXOutput) Unlock(pubKeyHash []byte) bool { + return bytes.Compare(out.ScriptPubKey, pubKeyHash) == 0 +} + +// NewTXOutput create a new TXOutput +func NewTXOutput(value int, address string) *TXOutput { + txo := &TXOutput{value, nil} + txo.Lock([]byte(address)) + + return txo +} diff --git a/wallet.go b/wallet.go index ca28c4f..d527988 100644 --- a/wallet.go +++ b/wallet.go @@ -1,16 +1,11 @@ package main import ( - "bytes" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/sha256" - "encoding/gob" - "fmt" - "io/ioutil" "log" - "os" "golang.org/x/crypto/ripemd160" ) @@ -24,9 +19,12 @@ type Wallet struct { PublicKey []byte } -// Wallets stores a collection of wallets -type Wallets struct { - Wallets map[string]*Wallet +// NewWallet creates and returns a Wallet +func NewWallet() *Wallet { + private, public := newKeyPair() + wallet := Wallet{private, public} + + return &wallet } // GetAddress returns wallet address @@ -42,14 +40,6 @@ func (w Wallet) GetAddress() []byte { return address } -// NewWallet creates and returns a Wallet -func NewWallet() *Wallet { - private, public := newKeyPair() - wallet := Wallet{private, public} - - return &wallet -} - func newKeyPair() (ecdsa.PrivateKey, []byte) { curve := elliptic.P256() private, err := ecdsa.GenerateKey(curve, rand.Reader) @@ -61,84 +51,6 @@ func newKeyPair() (ecdsa.PrivateKey, []byte) { return *private, pubKey } -// CreateWallet adds a Wallet to Wallets -func (ws *Wallets) CreateWallet() string { - wallet := NewWallet() - address := fmt.Sprintf("%s", wallet.GetAddress()) - - ws.Wallets[address] = wallet - - return address -} - -// SaveToFile saves wallets to a file -func (ws Wallets) SaveToFile() { - var content bytes.Buffer - - gob.Register(elliptic.P256()) - - encoder := gob.NewEncoder(&content) - err := encoder.Encode(ws) - if err != nil { - log.Panic(err) - } - - err = ioutil.WriteFile(walletFile, content.Bytes(), 0644) - if err != nil { - log.Panic(err) - } -} - -// LoadFromFile loads wallets from the file -func (ws *Wallets) LoadFromFile() error { - if _, err := os.Stat(walletFile); os.IsNotExist(err) { - return err - } - - fileContent, err := ioutil.ReadFile(walletFile) - if err != nil { - log.Panic(err) - } - - var wallets Wallets - gob.Register(elliptic.P256()) - decoder := gob.NewDecoder(bytes.NewReader(fileContent)) - err = decoder.Decode(&wallets) - if err != nil { - log.Panic(err) - } - - ws.Wallets = wallets.Wallets - - return nil -} - -// GetAddresses returns an array of addresses stored in the wallet file -func (ws *Wallets) GetAddresses() []string { - var addresses []string - - for address := range ws.Wallets { - addresses = append(addresses, address) - } - - return addresses -} - -// GetWallet returns a Wallet by its address -func (ws Wallets) GetWallet(address string) Wallet { - return *ws.Wallets[address] -} - -// NewWallets creates Wallets and fills it from a file if it exists -func NewWallets() (*Wallets, error) { - wallets := Wallets{} - wallets.Wallets = make(map[string]*Wallet) - - err := wallets.LoadFromFile() - - return &wallets, err -} - // HashPubKey hashes public key func HashPubKey(pubKey []byte) []byte { publicSHA256 := sha256.Sum256(pubKey) diff --git a/wallets.go b/wallets.go new file mode 100644 index 0000000..e5b2134 --- /dev/null +++ b/wallets.go @@ -0,0 +1,94 @@ +package main + +import ( + "bytes" + "crypto/elliptic" + "encoding/gob" + "fmt" + "io/ioutil" + "log" + "os" +) + +// Wallets stores a collection of wallets +type Wallets struct { + Wallets map[string]*Wallet +} + +// NewWallets creates Wallets and fills it from a file if it exists +func NewWallets() (*Wallets, error) { + wallets := Wallets{} + wallets.Wallets = make(map[string]*Wallet) + + err := wallets.LoadFromFile() + + return &wallets, err +} + +// CreateWallet adds a Wallet to Wallets +func (ws *Wallets) CreateWallet() string { + wallet := NewWallet() + address := fmt.Sprintf("%s", wallet.GetAddress()) + + ws.Wallets[address] = wallet + + return address +} + +// GetAddresses returns an array of addresses stored in the wallet file +func (ws *Wallets) GetAddresses() []string { + var addresses []string + + for address := range ws.Wallets { + addresses = append(addresses, address) + } + + return addresses +} + +// GetWallet returns a Wallet by its address +func (ws Wallets) GetWallet(address string) Wallet { + return *ws.Wallets[address] +} + +// LoadFromFile loads wallets from the file +func (ws *Wallets) LoadFromFile() error { + if _, err := os.Stat(walletFile); os.IsNotExist(err) { + return err + } + + fileContent, err := ioutil.ReadFile(walletFile) + if err != nil { + log.Panic(err) + } + + var wallets Wallets + gob.Register(elliptic.P256()) + decoder := gob.NewDecoder(bytes.NewReader(fileContent)) + err = decoder.Decode(&wallets) + if err != nil { + log.Panic(err) + } + + ws.Wallets = wallets.Wallets + + return nil +} + +// SaveToFile saves wallets to a file +func (ws Wallets) SaveToFile() { + var content bytes.Buffer + + gob.Register(elliptic.P256()) + + encoder := gob.NewEncoder(&content) + err := encoder.Encode(ws) + if err != nil { + log.Panic(err) + } + + err = ioutil.WriteFile(walletFile, content.Bytes(), 0644) + if err != nil { + log.Panic(err) + } +}