"context"
"encoding/binary"
"encoding/json"
- "sort"
"strings"
"sync"
"time"
)
const (
- maxAccountCache = 1000
- aliasPrefix = "ALI:"
- accountPrefix = "ACC:"
- accountCPPrefix = "ACP:"
- indexPrefix = "ACIDX:"
+ maxAccountCache = 1000
+ aliasPrefix = "ALI:"
+ accountPrefix = "ACC:"
+ accountCPPrefix = "ACP:"
+ contractIndexPrefix = "ACPI:"
)
-var miningAddressKey = []byte("miningAddress")
+var (
+ miningAddressKey = []byte("miningAddress")
+ accountIndexKey = []byte("accountIndex")
+)
// pre-define errors for supporting bytom errorFormatter
var (
return []byte(aliasPrefix + name)
}
-func indexKeys(xpubs []chainkd.XPub) []byte {
- xpubStrings := make([]string, len(xpubs))
- for i, xpub := range xpubs {
- xpubStrings[i] = xpub.String()
- }
- sort.Strings(xpubStrings)
- suffix := strings.Join(xpubStrings, "")
-
- return []byte(indexPrefix + suffix)
-}
-
//Key account store prefix
func Key(name string) []byte {
return []byte(accountPrefix + name)
return append([]byte(accountCPPrefix), hash[:]...)
}
+func contractIndexKey(accountID string) []byte {
+ return append([]byte(contractIndexPrefix), []byte(accountID)...)
+}
+
func convertUnit64ToBytes(nextIndex uint64) []byte {
buf := make([]byte, 8)
binary.PutUvarint(buf, nextIndex)
Alias string
}
-func (m *Manager) getNextXpubsIndex(xpubs []chainkd.XPub) uint64 {
+func (m *Manager) getNextAccountIndex() uint64 {
m.accIndexMu.Lock()
defer m.accIndexMu.Unlock()
var nextIndex uint64 = 1
- if rawIndexBytes := m.db.Get(indexKeys(xpubs)); rawIndexBytes != nil {
+ if rawIndexBytes := m.db.Get(accountIndexKey); rawIndexBytes != nil {
nextIndex = convertBytesToUint64(rawIndexBytes) + 1
}
- m.db.Set(indexKeys(xpubs), convertUnit64ToBytes(nextIndex))
+ m.db.Set(accountIndexKey, convertUnit64ToBytes(nextIndex))
+ return nextIndex
+}
+
+func (m *Manager) getNextContractIndex(accountID string) uint64 {
+ m.accIndexMu.Lock()
+ defer m.accIndexMu.Unlock()
+
+ var nextIndex uint64 = 1
+ if rawIndexBytes := m.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
+ nextIndex = convertBytesToUint64(rawIndexBytes) + 1
+ }
+ m.db.Set(contractIndexKey(accountID), convertUnit64ToBytes(nextIndex))
return nextIndex
}
return nil, ErrDuplicateAlias
}
- signer, err := signers.Create("account", xpubs, quorum, m.getNextXpubsIndex(xpubs))
+ signer, err := signers.Create("account", xpubs, quorum, m.getNextAccountIndex())
id := signers.IDGenerate()
if err != nil {
return nil, errors.Wrap(err)
}
func (m *Manager) createP2PKH(ctx context.Context, account *Account, change bool) (*CtrlProgram, error) {
- idx := m.getNextXpubsIndex(account.Signer.XPubs)
+ idx := m.getNextContractIndex(account.ID)
path := signers.Path(account.Signer, signers.AccountKeySpace, idx)
derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
derivedPK := derivedXPubs[0].PublicKey()
}
func (m *Manager) createP2SH(ctx context.Context, account *Account, change bool) (*CtrlProgram, error) {
- idx := m.getNextXpubsIndex(account.Signer.XPubs)
+ idx := m.getNextContractIndex(account.ID)
path := signers.Path(account.Signer, signers.AccountKeySpace, idx)
derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
derivedPKs := chainkd.XPubKeys(derivedXPubs)
--- /dev/null
+// Package account stores and tracks accounts within a Chain Core.
+package account
+
+import (
+ "encoding/json"
+)
+
+type AccountSlice struct {
+ Account *Account
+ ContractIndex uint64
+}
+
+type AccountImage struct {
+ AccountSlice []*AccountSlice
+ AccountIndex uint64
+}
+
+func (m *Manager) Backup() (*AccountImage, error) {
+ accountSlices := []*AccountSlice{}
+ accountIter := m.db.IteratorPrefix([]byte(accountPrefix))
+ defer accountIter.Release()
+
+ for accountIter.Next() {
+ accountSlice := &AccountSlice{}
+ if err := json.Unmarshal(accountIter.Value(), accountSlice.Account); err != nil {
+ return nil, err
+ }
+
+ accountSlice.ContractIndex = m.getNextContractIndex(accountSlice.Account.ID)
+ accountSlices = append(accountSlices, accountSlice)
+ }
+
+ accountImage := &AccountImage{
+ AccountSlice: accountSlices,
+ AccountIndex: m.getNextAccountIndex(),
+ }
+ return accountImage, nil
+}
+
+func (m *Manager) Restore(image *AccountImage) error {
+ if localIndex := m.getNextAccountIndex(); localIndex > image.AccountIndex {
+ image.AccountIndex = localIndex
+ }
+
+ storeBatch := m.db.NewBatch()
+ for _, accountSlice := range image.AccountSlice {
+ rawAccount, err := json.Marshal(accountSlice.Account)
+ if err != nil {
+ return ErrMarshalAccount
+ }
+
+ if existed := m.db.Get(aliasKey(accountSlice.Account.Alias)); existed != nil {
+ return ErrDuplicateAlias
+ }
+
+ accountID := Key(accountSlice.Account.ID)
+ storeBatch.Set(accountID, rawAccount)
+ storeBatch.Set(aliasKey(accountSlice.Account.Alias), accountID)
+ storeBatch.Set(contractIndexKey(accountSlice.Account.ID), convertUnit64ToBytes(accountSlice.ContractIndex))
+ }
+
+ storeBatch.Set(accountIndexKey, convertUnit64ToBytes(image.AccountIndex))
+ storeBatch.Write()
+ return nil
+}
m.Handle("/list-balances", jsonHandler(a.listBalances))
m.Handle("/list-unspent-outputs", jsonHandler(a.listUnspentOutputs))
+
+ m.Handle("/backup-wallet", jsonHandler(a.backupWalletImage))
+ m.Handle("/restore-wallet", jsonHandler(a.restoreWalletImage))
} else {
log.Warn("Please enable wallet")
}
package api
import (
+ "context"
+
+ "github.com/bytom/account"
+ "github.com/bytom/asset"
+ "github.com/bytom/blockchain/pseudohsm"
"github.com/bytom/errors"
)
func (a *API) walletError() Response {
return NewErrorResponse(errors.New("wallet not found, please check that the wallet is open"))
}
+
+type WalletImage struct {
+ AccountImage *account.AccountImage
+ AssetImage *asset.AssetImage
+ KeyImages []*pseudohsm.KeyImage
+}
+
+func (a *API) restoreWalletImage(ctx context.Context, image WalletImage) Response {
+ if err := a.wallet.Hsm.Restore(image.KeyImages); err != nil {
+ return NewErrorResponse(err)
+ }
+ if err := a.wallet.AssetReg.Restore(image.AssetImage); err != nil {
+ return NewErrorResponse(err)
+ }
+ if err := a.wallet.AccountMgr.Restore(image.AccountImage); err != nil {
+ return NewErrorResponse(err)
+ }
+ return NewSuccessResponse(nil)
+}
+
+func (a *API) backupWalletImage() Response {
+ keyImages, err := a.wallet.Hsm.Backup()
+ if err != nil {
+ return NewErrorResponse(err)
+ }
+ assetImage, err := a.wallet.AssetReg.Backup()
+ if err != nil {
+ return NewErrorResponse(err)
+ }
+ accountImage, err := a.wallet.AccountMgr.Backup()
+ if err != nil {
+ return NewErrorResponse(err)
+ }
+
+ image := &WalletImage{
+ KeyImages: keyImages,
+ AssetImage: assetImage,
+ AccountImage: accountImage,
+ }
+ return NewSuccessResponse(image)
+}
AliasPrefix = "ALS:"
//ExternalAssetPrefix is external definition assets prefix
ExternalAssetPrefix = "EXA"
- indexPrefix = "ASSIDX:"
)
+var assetIndexKey = []byte("ASSETINDEX")
+
func initNativeAsset() {
signer := &signers.Signer{Type: "internal"}
alias := consensus.BTMAlias
return []byte(assetPrefix + name)
}
-func indexKey(xpub chainkd.XPub) []byte {
- return []byte(indexPrefix + xpub.String())
-}
-
//CalcExtAssetKey return store external assets key
func CalcExtAssetKey(id *bc.AssetID) []byte {
name := id.String()
DefinitionMap map[string]interface{} `json:"definition"`
}
-func (reg *Registry) getNextAssetIndex(xpubs []chainkd.XPub) (*uint64, error) {
+func (reg *Registry) getNextAssetIndex() (uint64, error) {
reg.assetIndexMu.Lock()
defer reg.assetIndexMu.Unlock()
var nextIndex uint64 = 1
- if rawIndex := reg.db.Get(indexKey(xpubs[0])); rawIndex != nil {
+ if rawIndex := reg.db.Get(assetIndexKey); rawIndex != nil {
nextIndex = binary.LittleEndian.Uint64(rawIndex) + 1
}
+ reg.saveNextAssetIndex(nextIndex)
+ return nextIndex, nil
+}
+
+func (reg *Registry) saveNextAssetIndex(nextIndex uint64) {
buf := make([]byte, 8)
binary.LittleEndian.PutUint64(buf, nextIndex)
- reg.db.Set(indexKey(xpubs[0]), buf)
-
- return &nextIndex, nil
+ reg.db.Set(assetIndexKey, buf)
}
// Define defines a new Asset.
return nil, ErrDuplicateAlias
}
- nextAssetIndex, err := reg.getNextAssetIndex(xpubs)
+ nextAssetIndex, err := reg.getNextAssetIndex()
if err != nil {
return nil, errors.Wrap(err, "get asset index error")
}
- assetSigner, err := signers.Create("asset", xpubs, quorum, *nextAssetIndex)
+ assetSigner, err := signers.Create("asset", xpubs, quorum, nextAssetIndex)
if err != nil {
return nil, err
}
--- /dev/null
+package asset
+
+import (
+ "encoding/json"
+)
+
+type AssetImage struct {
+ Assets []*Asset
+ AssetIndex uint64
+}
+
+func (reg *Registry) Backup() (*AssetImage, error) {
+ assetIndex, err := reg.getNextAssetIndex()
+ if err != nil {
+ return nil, err
+ }
+
+ assets := []*Asset{}
+ assetIter := reg.db.IteratorPrefix([]byte(assetPrefix))
+ defer assetIter.Release()
+
+ for assetIter.Next() {
+ asset := &Asset{}
+ if err := json.Unmarshal(assetIter.Value(), asset); err != nil {
+ return nil, err
+ }
+ assets = append(assets, asset)
+ }
+
+ assetImage := &AssetImage{
+ AssetIndex: assetIndex,
+ Assets: assets,
+ }
+ return assetImage, nil
+}
+
+func (reg *Registry) Restore(image *AssetImage) error {
+ localIndex, err := reg.getNextAssetIndex()
+ if err != nil {
+ return err
+ }
+
+ if localIndex > image.AssetIndex {
+ image.AssetIndex = localIndex
+ }
+
+ storeBatch := reg.db.NewBatch()
+ for _, asset := range image.Assets {
+ if existed := reg.db.Get(AliasKey(*asset.Alias)); existed != nil {
+ return ErrDuplicateAlias
+ }
+
+ rawAsset, err := json.Marshal(asset)
+ if err != nil {
+ return err
+ }
+
+ storeBatch.Set(AliasKey(*asset.Alias), []byte(asset.AssetID.String()))
+ storeBatch.Set(Key(&asset.AssetID), rawAsset)
+ }
+ storeBatch.Write()
+ reg.saveNextAssetIndex(image.AssetIndex)
+ return nil
+}
--- /dev/null
+// Package pseudohsm provides a pseudo HSM for development environments.
+package pseudohsm
+
+import (
+ "io/ioutil"
+ "path/filepath"
+)
+
+type KeyImage struct {
+ XPub XPub `json:"xpub"`
+ XKey []byte `json:"xkey"`
+}
+
+func (h *HSM) Backup() ([]*KeyImage, error) {
+ images := []*KeyImage{}
+ xpubs := h.cache.keys()
+ for _, xpub := range xpubs {
+ xKey, err := ioutil.ReadFile(xpub.File)
+ if err != nil {
+ return nil, err
+ }
+
+ images = append(images, &KeyImage{XPub: xpub, XKey: xKey})
+ }
+ return images, nil
+}
+
+func (h *HSM) Restore(images []*KeyImage) error {
+ for _, image := range images {
+ if ok := h.cache.hasAlias(image.XPub.Alias); ok {
+ return ErrDuplicateKeyAlias
+ }
+
+ fileName := filepath.Base(image.XPub.File)
+ image.XPub.File = h.keyStore.JoinPath(fileName)
+ if err := writeKeyFile(image.XPub.File, image.XKey); err != nil {
+ return nil
+ }
+ h.cache.add(image.XPub)
+ }
+ return nil
+}
type XPub struct {
Alias string `json:"alias"`
XPub chainkd.XPub `json:"xpub"`
- File string `json:"-"`
+ File string `json:"file"`
}
// New method for HSM struct
func (h *HSM) HasKey(xprv chainkd.XPrv) bool {
return h.cache.hasKey(xprv.XPub())
}
-
-//ImportXPrvKey import XPrv to chainkd
-func (h *HSM) ImportXPrvKey(auth string, alias string, xprv chainkd.XPrv) (*XPub, bool, error) {
- if ok := h.cache.hasAlias(alias); ok {
- return nil, false, ErrDuplicateKeyAlias
- }
-
- if ok := h.cache.hasKey(xprv.XPub()); ok {
- return nil, false, ErrDuplicateKey
- }
-
- xpub := xprv.XPub()
- id := uuid.NewRandom()
- key := &XKey{
- ID: id,
- KeyType: "bytom_kd",
- XPub: xpub,
- XPrv: xprv,
- Alias: alias,
- }
- file := h.keyStore.JoinPath(keyFileName(key.ID.String()))
- if err := h.keyStore.StoreKey(file, key, auth); err != nil {
- return nil, false, errors.Wrap(err, "storing keys")
- }
-
- h.cache.add(XPub{XPub: xpub, Alias: alias, File: file})
- return &XPub{XPub: xpub, Alias: alias, File: file}, true, nil
-}