import (
"context"
+ "encoding/binary"
"encoding/json"
"fmt"
"sync"
aliasPrefix = "ALI:"
accountPrefix = "ACC:"
accountCPPrefix = "ACP:"
+ keyNextIndex = "NextIndex"
)
// pre-define errors for supporting bytom errorFormatter
// NewManager creates a new account manager
func NewManager(walletDB dbm.DB, chain *protocol.Chain) *Manager {
+ var nextIndex uint64
+ if index := walletDB.Get([]byte(keyNextIndex)); index != nil {
+ nextIndex = uint64(binary.LittleEndian.Uint64(index))
+ }
return &Manager{
- db: walletDB,
- chain: chain,
- utxoDB: newReserver(chain, walletDB),
- cache: lru.New(maxAccountCache),
- aliasCache: lru.New(maxAccountCache),
- delayedACPs: make(map[*txbuilder.TemplateBuilder][]*CtrlProgram),
+ db: walletDB,
+ chain: chain,
+ utxoDB: newReserver(chain, walletDB),
+ cache: lru.New(maxAccountCache),
+ aliasCache: lru.New(maxAccountCache),
+ delayedACPs: make(map[*txbuilder.TemplateBuilder][]*CtrlProgram),
+ acpIndexNext: nextIndex,
}
}
return script, nil
}
+func saveIndex(db dbm.DB, index uint64) {
+ buf := make([]byte, 8)
+ binary.LittleEndian.PutUint64(buf, index)
+ db.Set([]byte(keyNextIndex), buf)
+}
+
func (m *Manager) nextIndex(ctx context.Context) (uint64, error) {
m.acpMu.Lock()
defer m.acpMu.Unlock()
- //TODO: fix this part, really serious security breach
- if m.acpIndexNext >= m.acpIndexCap {
- const incrby = 10000 // start 1,increments by 10,000
- if m.acpIndexCap <= incrby {
- m.acpIndexCap = incrby + 1
- } else {
- m.acpIndexCap += incrby
- }
- m.acpIndexNext = m.acpIndexCap - incrby
- }
-
n := m.acpIndexNext
m.acpIndexNext++
+ saveIndex(m.db, m.acpIndexNext)
return n, nil
}
// listKeyMaxAliases limits the alias filter to a sane maximum size.
const listKeyMaxAliases = 200
+// pre-define errors for supporting bytom errorFormatter
var (
ErrDuplicateKeyAlias = errors.New("duplicate key alias")
ErrInvalidAfter = errors.New("invalid after")
// xprv with the given path (but does not store the new xprv), and
// signs the given msg.
func (h *HSM) XSign(xpub chainkd.XPub, path [][]byte, msg []byte, auth string) ([]byte, error) {
- xprv, err := h.loadChainKDKey(xpub, auth)
+ xprv, err := h.LoadChainKDKey(xpub, auth)
if err != nil {
return nil, err
}
return xprv.Sign(msg), nil
}
-func (h *HSM) loadChainKDKey(xpub chainkd.XPub, auth string) (xprv chainkd.XPrv, err error) {
+//LoadChainKDKey get xprv from xpub
+func (h *HSM) LoadChainKDKey(xpub chainkd.XPub, auth string) (xprv chainkd.XPrv, err error) {
h.cacheMu.Lock()
defer h.cacheMu.Unlock()
}
return h.keyStore.StoreKey(xpb.File, xkey, newAuth)
}
+
+//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
+ }
+
+ 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
+}
bcr.handler.ServeHTTP(rw, req)
}
-// BuildHander is json rpc handler
+//BuildHander build json rpc handler
func (bcr *BlockchainReactor) BuildHander() {
m := bcr.mux
if bcr.accounts != nil && bcr.assets != nil {
//hsm api
m.Handle("/create-key", jsonHandler(bcr.pseudohsmCreateKey))
+ m.Handle("/export-private-key", jsonHandler(bcr.walletExportKey))
+ m.Handle("/import-private-key", jsonHandler(bcr.walletImportKey))
m.Handle("/list-keys", jsonHandler(bcr.pseudohsmListKeys))
m.Handle("/delete-key", jsonHandler(bcr.pseudohsmDeleteKey))
m.Handle("/sign-transactions", jsonHandler(bcr.pseudohsmSignTemplates))
--- /dev/null
+package blockchain
+
+import (
+ "context"
+
+ "github.com/bytom/crypto/ed25519/chainkd"
+)
+
+func (bcr *BlockchainReactor) walletExportKey(ctx context.Context, in struct {
+ Password string
+ XPub chainkd.XPub
+}) interface{} {
+ key, err := bcr.wallet.ExportAccountPrivKey(bcr.hsm, in.XPub, in.Password)
+ if err != nil {
+ return err.Error()
+ }
+ return key
+}
+
+func (bcr *BlockchainReactor) walletImportKey(ctx context.Context, in struct {
+ Alias string
+ Password string
+ XPrv chainkd.XPrv
+ Index uint64
+}) interface{} {
+ xpub, err := bcr.wallet.ImportAccountPrivKey(bcr.hsm, in.XPrv, in.Alias, in.Password, in.Index)
+ if err != nil {
+ return err.Error()
+ }
+ return xpub
+}
import (
"encoding/json"
+ "time"
+ "github.com/btcsuite/btcutil/base58"
log "github.com/sirupsen/logrus"
"github.com/tendermint/tmlibs/db"
+ "github.com/bytom/blockchain/account"
+ "github.com/bytom/blockchain/asset"
+ "github.com/bytom/blockchain/pseudohsm"
+ "github.com/bytom/crypto/ed25519/chainkd"
+ "github.com/bytom/crypto/sha3pool"
"github.com/bytom/protocol"
"github.com/bytom/protocol/bc"
"github.com/bytom/protocol/bc/legacy"
)
+//SINGLE single sign
+const SINGLE = 1
+
var walletKey = []byte("walletInfo")
//StatusInfo is base valid block info to handle orphan block rollback
//Wallet is related to storing account unspent outputs
type Wallet struct {
- DB db.DB
- status StatusInfo
+ DB db.DB
+ status StatusInfo
+ AccountMgr *account.Manager
+ AssetReg *asset.Registry
+ chain *protocol.Chain
+ rescanProgress chan bool
}
//NewWallet return a new wallet instance
-func NewWallet(walletDB db.DB) *Wallet {
+func NewWallet(walletDB db.DB, account *account.Manager, asset *asset.Registry, chain *protocol.Chain) *Wallet {
w := &Wallet{
- DB: walletDB,
+ DB: walletDB,
+ AccountMgr: account,
+ AssetReg: asset,
+ chain: chain,
}
walletInfo, err := w.GetWalletInfo()
if err != nil {
}
w.status.Height = walletInfo.Height
w.status.Hash = walletInfo.Hash
+ rescanProgress := make(chan bool)
+ w.rescanProgress = rescanProgress
return w
}
storeBatch := w.DB.NewBatch()
LOOP:
-
+ getRescanNotification(w)
for !c.InMainChain(w.status.Height, w.status.Hash) {
if block, err = c.GetBlockByHash(&w.status.Hash); err != nil {
log.WithField("err", err).Error("get block by hash")
//next loop will save
w.status.Height = block.Height
w.status.Hash = block.Hash()
-
indexTransactions(&storeBatch, block, w)
buildAccountUTXOs(&storeBatch, block, w)
//goto next loop
goto LOOP
}
+
+func getRescanNotification(w *Wallet) {
+ select {
+ case <-w.rescanProgress:
+ w.status.Height = 1
+ block, _ := w.chain.GetBlockByHeight(w.status.Height)
+ w.status.Hash = block.Hash()
+ default:
+ return
+ }
+}
+
+// ExportAccountPrivKey exports the account private key as a WIF for encoding as a string
+// in the Wallet Import Formt.
+func (w *Wallet) ExportAccountPrivKey(hsm *pseudohsm.HSM, xpub chainkd.XPub, auth string) (*string, error) {
+ xprv, err := hsm.LoadChainKDKey(xpub, auth)
+ if err != nil {
+ return nil, err
+ }
+ var hashed [32]byte
+ sha3pool.Sum256(hashed[:], xprv[:])
+
+ tmp := append(xprv[:], hashed[:4]...)
+ res := base58.Encode(tmp)
+ return &res, nil
+}
+
+// ImportAccountPrivKey imports the account key in the Wallet Import Formt.
+func (w *Wallet) ImportAccountPrivKey(hsm *pseudohsm.HSM, xprv chainkd.XPrv, alias, auth string, index uint64) (*pseudohsm.XPub, error) {
+ xpub, _, err := hsm.ImportXPrvKey(auth, alias, xprv)
+ if err != nil {
+ return nil, err
+ }
+ var xpubs []chainkd.XPub
+ xpubs = append(xpubs, xpub.XPub)
+ account, err := w.AccountMgr.Create(nil, xpubs, SINGLE, alias, nil, "")
+ if err != nil {
+ return nil, err
+ }
+ if err := recoveryAccountWalletDB(w, account, &w.DB, xpub, index); err != nil {
+ return nil, err
+ }
+ return xpub, nil
+}
+
+func recoveryAccountWalletDB(w *Wallet, account *account.Account, DB *db.DB, XPub *pseudohsm.XPub, index uint64) error {
+ if err := createProgram(w, account, DB, XPub, index); err != nil {
+ return err
+ }
+ rescanBlocks(w)
+ return nil
+}
+
+func createProgram(w *Wallet, account *account.Account, DB *db.DB, XPub *pseudohsm.XPub, index uint64) error {
+ for i := uint64(0); i < index; i++ {
+ _, err := w.AccountMgr.CreateControlProgram(nil, account.ID, true, time.Now())
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+//WalletUpdate process every valid block and reverse every invalid block which need to rollback
+func rescanBlocks(w *Wallet) {
+ w.rescanProgress <- true
+}
BytomcliCmd.AddCommand(createKeyCmd)
BytomcliCmd.AddCommand(deleteKeyCmd)
BytomcliCmd.AddCommand(listKeysCmd)
+ BytomcliCmd.AddCommand(exportPrivateCmd)
+ BytomcliCmd.AddCommand(importPrivateCmd)
BytomcliCmd.AddCommand(isMiningCmd)
package commands
import (
+ "bytes"
"context"
"encoding/hex"
"strconv"
+ "github.com/btcsuite/btcutil/base58"
"github.com/spf13/cobra"
jww "github.com/spf13/jwalterweatherman"
"github.com/bytom/crypto/ed25519/chainkd"
+ "github.com/bytom/crypto/sha3pool"
)
var createKeyCmd = &cobra.Command{
}
},
}
+
+var exportPrivateCmd = &cobra.Command{
+ Use: "export-private-key",
+ Short: "export the private key",
+ Run: func(cmd *cobra.Command, args []string) {
+ if len(args) != 2 {
+ jww.ERROR.Println("error: export-private-key args not vaild export-private-key password xpub")
+ return
+ }
+
+ type Key struct {
+ Password string
+ XPub chainkd.XPub
+ }
+ var key Key
+ xpub := new(chainkd.XPub)
+ data, err := hex.DecodeString(args[1])
+ if err != nil {
+ jww.ERROR.Println("error: export-private-key args not vaild", err)
+ }
+ copy(xpub[:], data)
+
+ key.Password = args[0]
+ key.XPub = *xpub
+
+ var response interface{}
+
+ client := mustRPCClient()
+ client.Call(context.Background(), "/export-private-key", &key, &response)
+ res := base58.Decode(response.(string))
+ if len(res) != 68 {
+ jww.ERROR.Println("export private error")
+ return
+ }
+ var hashed [32]byte
+ sha3pool.Sum256(hashed[:], res[:64])
+ rest := bytes.Compare(hashed[:4], res[64:])
+ if rest == 0 {
+ jww.FEEDBACK.Printf("priv: %v\n", response)
+ return
+ }
+ jww.ERROR.Println("export private error")
+ return
+ },
+}
+
+var importPrivateCmd = &cobra.Command{
+ Use: "import-private-key",
+ Short: "import the private key",
+ Run: func(cmd *cobra.Command, args []string) {
+ if len(args) != 4 {
+ jww.ERROR.Println("error: import-private-key args not vaild import-private-key Alias Password Priv Index")
+ return
+ }
+
+ type Key struct {
+ Alias string
+ Password string
+ XPrv chainkd.XPrv
+ Index uint64
+ }
+
+ privhash := base58.Decode(args[2])
+ if len(privhash) != 68 {
+ jww.ERROR.Println("wif priv length error")
+ return
+ }
+ var hashed [32]byte
+
+ sha3pool.Sum256(hashed[:], privhash[:64])
+
+ if res := bytes.Compare(hashed[:4], privhash[64:]); res != 0 {
+ jww.ERROR.Println("wif priv hash error")
+ }
+ var key Key
+ key.Alias = args[0]
+ key.Password = args[1]
+ key.Index, _ = strconv.ParseUint(args[3], 10, 64)
+ copy(key.XPrv[:], privhash[:64])
+
+ var response interface{}
+
+ client := mustRPCClient()
+ client.Call(context.Background(), "/import-private-key", &key, &response)
+ jww.FEEDBACK.Printf("%v\n", response)
+ },
+}
accounts = account.NewManager(walletDB, chain)
assets = asset.NewRegistry(walletDB, chain)
- wallet = w.NewWallet(walletDB)
+ wallet = w.NewWallet(walletDB, accounts, assets, chain)
go wallet.WalletUpdate(chain)