Commit 4d23c713 authored by David Vorick's avatar David Vorick

implement walletSeedHandlerGET

This actually resulted in a modification of the entropy-mnemonics
library, and a modification to the wallet interface. Previously, the
master key was required to load all seeds. That requirement has been
removed, and I believe in doing so more safetly has been added to the
way that seeds are determined.
parent 9075f397
......@@ -4,6 +4,8 @@ import (
"net/http"
"strconv"
"github.com/NebulousLabs/entropy-mnemonics"
"github.com/NebulousLabs/Sia/modules"
"github.com/NebulousLabs/Sia/types"
)
......@@ -13,19 +15,42 @@ import (
type WalletGET struct {
Encrypted bool `json:"encrypted"`
Unlocked bool `json:"unlocked"`
ConfirmedSiacoinBalance types.Currency `json:"confirmedSiacoinBalance"`
UnconfirmedOutgoingSiacoins types.Currency `json:"unconfirmedOutgoingSiacoins"`
UnconfirmedIncomingSiacoins types.Currency `json:"unconfirmedIncomingSiacoins"`
SiafundBalance types.Currency `json:"siafundBalance"`
SiacoinClaimBalance types.Currency `json:"siacoinClaimBalance"`
}
// WalletHistoryGet contains wallet history.
// WalletHistoryGet contains wallet transaction history.
type WalletHistoryGET struct {
UnconfirmedTransactions []modules.WalletTransaction `json:"unconfirmedTransactions"`
ConfirmedTransactions []modules.WalletTransaction `json:"confirmedTransactions"`
}
// WalletSeedGet contains the seeds used by the wallet.
type WalletSeedGET struct {
PrimarySeed string `json:"primarySeed"`
AddressesRemaining int `json:"AddressesRemaining"`
AllSeeds []string `json:"allSeeds"`
}
// walletHandlerGET handles a GET request to /wallet.
func (srv *Server) walletHandlerGET(w http.ResponseWriter, req *http.Request) {
siacoinBal, siafundBal, siaclaimBal := srv.wallet.ConfirmedBalance()
siacoinsOut, siacoinsIn := srv.wallet.UnconfirmedBalance()
writeJSON(w, WalletGET{
Encrypted: srv.wallet.Encrypted(),
Unlocked: srv.wallet.Unlocked(),
ConfirmedSiacoinBalance: siacoinBal,
UnconfirmedOutgoingSiacoins: siacoinsOut,
UnconfirmedIncomingSiacoins: siacoinsIn,
SiafundBalance: siafundBal,
SiacoinClaimBalance: siaclaimBal,
})
}
......@@ -110,3 +135,41 @@ func (srv *Server) walletHistoryHandler(w http.ResponseWriter, req *http.Request
}
srv.walletHistoryHandlerGETAddr(w, req, addr)
}
// walletSeedHandlerGET handles a GET request to /wallet/seed.
func (srv *Server) walletSeedHandlerGET(w http.ResponseWriter, req *http.Request) {
dictionary := mnemonics.DictionaryID(req.FormValue("dictionary"))
if dictionary == "" {
dictionary = mnemonics.English
}
// Get the primary seed information.
primarySeed, progress, err := srv.wallet.PrimarySeed()
if err != nil {
writeError(w, "error after call to /wallet/seed: "+err.Error(), http.StatusBadRequest)
return
}
primarySeedStr, err := modules.SeedToString(primarySeed, dictionary)
if err != nil {
writeError(w, "error after call to /wallet/seed: "+err.Error(), http.StatusBadRequest)
return
}
// Get the list of seeds known to the wallet.
allSeeds := srv.wallet.AllSeeds()
var allSeedsStrs []string
for _, seed := range allSeeds {
str, err := modules.SeedToString(seed, dictionary)
if err != nil {
writeError(w, "error after call to /wallet/seed: "+err.Error(), http.StatusBadRequest)
return
}
allSeedsStrs = append(allSeedsStrs, str)
}
writeJSON(w, WalletSeedGET{
PrimarySeed: primarySeedStr,
AddressesRemaining: int(modules.PublicKeysPerSeed - progress),
AllSeeds: allSeedsStrs,
})
}
......@@ -234,7 +234,7 @@ Response:
struct {
primarySeed string
addressesRemaining int
backupSeeds []string
allSeeds []string
}
```
'primarySeed' is the seed that is actively being used to generate new addresses
......@@ -243,8 +243,10 @@ for the wallet.
'addressesRemaining' is the number of addresses that remain in the primary seed
until exhaustion has been reached and no more addresses will be generated.
'backupSeeds' is a list of seeds that are no longer used to generate new
addresses, but are still tracked can have spendable outputs.
'allSeeds' is a list of all seeds that the wallet references when scanning the
blockchain for outputs. The wallet is able to spend any output generated by any
of the seeds, however only the primary seed is being used to generate new
addresses.
A seed is an encoded version of a 128 bit random seed. The output is 15 words
chosen from a small dictionary as indicated by the input. The most common
......
package modules
import (
"bytes"
"errors"
"github.com/NebulousLabs/entropy-mnemonics"
"github.com/NebulousLabs/Sia/crypto"
"github.com/NebulousLabs/Sia/types"
)
......@@ -11,6 +14,8 @@ const (
WalletDir = "wallet"
PublicKeysPerSeed = 100
SeedChecksumSize = 6
)
var (
......@@ -182,7 +187,7 @@ type (
// wallet, including the primary seed. Only the primary seed is used to
// generate new addresses, but the wallet can spend funds sent to
// public keys generated by any of the seeds returned.
AllSeeds(crypto.TwofishKey) ([]Seed, error)
AllSeeds() []Seed
// ConfirmedBalance returns the confirmed balance of the wallet, minus
// any outgoing transactions. ConfirmedBalance will include unconfirmed
......@@ -256,3 +261,32 @@ type (
func CalculateWalletTransactionID(tid types.TransactionID, oid types.OutputID) WalletTransactionID {
return WalletTransactionID(crypto.HashAll(tid, oid))
}
// SeedToString converts a wallet seed to a human friendly string.
func SeedToString(seed Seed, did mnemonics.DictionaryID) (string, error) {
fullChecksum := crypto.HashObject(seed)
checksumSeed := append(seed[:], fullChecksum[:SeedChecksumSize]...)
phrase, err := mnemonics.ToPhrase(checksumSeed, did)
if err != nil {
return "", err
}
return phrase.String(), nil
}
// StringToSeed converts a string to a wallet seed.
func StringToSeed(str string, did mnemonics.DictionaryID) (Seed, error) {
// Decode the string into the checksummed byte slice.
checksumSeedBytes, err := mnemonics.FromString(str, did)
if err != nil {
return Seed{}, err
}
// Copy the seed from the checksummed slice.
var seed Seed
copy(seed[:], checksumSeedBytes)
fullChecksum := crypto.HashObject(seed)
if !bytes.Equal(fullChecksum[:SeedChecksumSize], checksumSeedBytes[crypto.EntropySize:]) {
return Seed{}, errors.New("seed failed checksum verification")
}
return seed, nil
}
......@@ -95,6 +95,7 @@ func (w *Wallet) integrateSeed(seed modules.Seed) {
spendableKey := generateSpendableKey(seed, i)
w.keys[spendableKey.unlockConditions.UnlockHash()] = spendableKey
}
w.seeds = append(w.seeds, seed)
}
// loadSeedFile loads an encrypted seed from disk, decrypting it and
......@@ -277,35 +278,9 @@ func (w *Wallet) RecoverSeed(masterKey crypto.TwofishKey, seed modules.Seed) err
return w.recoverSeed(masterKey, seed)
}
// AllSeeds returns a list of all seeds known to the wallet.
func (w *Wallet) AllSeeds(masterKey crypto.TwofishKey) ([]modules.Seed, error) {
// Scan for existing wallet seed files.
var seeds []modules.Seed
filesInfo, err := ioutil.ReadDir(w.persistDir)
if err != nil {
return nil, err
}
for _, fileInfo := range filesInfo {
if strings.HasSuffix(fileInfo.Name(), seedFileSuffix) {
// Open the seed file.
var seedFile SeedFile
err := persist.LoadFile(seedMetadata, &seedFile, fileInfo.Name())
if err != nil {
return nil, err
}
seed, err := decryptSeedFile(masterKey, seedFile)
if err != nil {
continue
}
// Check that the seed is actively being used by the wallet.
spendableKey := generateSpendableKey(seed, 0)
_, exists := w.keys[spendableKey.unlockConditions.UnlockHash()]
if !exists {
continue
}
seeds = append(seeds, seed)
}
}
return seeds, nil
// AllSeeds returns a list of all seeds known to and used by the wallet.
func (w *Wallet) AllSeeds() []modules.Seed {
lockID := w.mu.Lock()
defer w.mu.Unlock(lockID)
return w.seeds
}
......@@ -40,6 +40,7 @@ type Wallet struct {
consensusSetHeight types.BlockHeight
siafundPool types.Currency
seeds []modules.Seed
keys map[types.UnlockHash]spendableKey
siacoinOutputs map[types.SiacoinOutputID]types.SiacoinOutput
siafundOutputs map[types.SiafundOutputID]types.SiafundOutput
......
......@@ -387,7 +387,7 @@ func (t *Transaction) validSignatures(currentHeight BlockHeight) error {
}
default:
// If we don't recognize the identifier, assume that the signature
// If the identifier is not recognized, assume that the signature
// is valid. This allows more signature types to be added via soft
// forking.
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment