m.Handle("/delete-key", jsonHandler(a.pseudohsmDeleteKey))
m.Handle("/reset-key-password", jsonHandler(a.pseudohsmResetPassword))
- m.Handle("/export-private-key", jsonHandler(a.walletExportKey))
- m.Handle("/import-private-key", jsonHandler(a.walletImportKey))
- m.Handle("/import-key-progress", jsonHandler(a.keyImportProgress))
-
m.Handle("/build-transaction", jsonHandler(a.build))
m.Handle("/sign-transaction", jsonHandler(a.pseudohsmSignTemplates))
m.Handle("/submit-transaction", jsonHandler(a.submit))
package api
import (
- "bytes"
- "context"
-
- "github.com/tendermint/go-wire/data/base58"
-
- "github.com/bytom/crypto/ed25519/chainkd"
- "github.com/bytom/crypto/sha3pool"
"github.com/bytom/errors"
)
-//KeyImportParams private key import param
-type KeyImportParams struct {
- KeyAlias string `json:"alias"`
- Password string `json:"password"`
- XPrv string `json:"xprv"`
- Index uint64 `json:"index"`
- AccountAlias string `json:"account_alias"`
-}
-
-func (a *API) walletExportKey(ctx context.Context, in struct {
- Password string `json:"password"`
- XPub chainkd.XPub `json:"xpub"`
-}) Response {
- key, err := a.wallet.ExportAccountPrivKey(in.XPub, in.Password)
- if err != nil {
- return NewErrorResponse(err)
- }
-
- type privateKey struct {
- PrivateKey string `json:"private_key"`
- }
- return NewSuccessResponse(privateKey{PrivateKey: *key})
-}
-
-func (a *API) walletImportKey(ctx context.Context, in KeyImportParams) Response {
- rawData, err := base58.Decode(in.XPrv)
- if err != nil {
- return NewErrorResponse(err)
- }
-
- if len(rawData) != 68 {
- return NewErrorResponse(errors.New("invalid private key hash length"))
- }
-
- var hashed [32]byte
- sha3pool.Sum256(hashed[:], rawData[:64])
- if res := bytes.Compare(hashed[:4], rawData[64:]); res != 0 {
- return NewErrorResponse(errors.New("private hash error"))
- }
-
- var xprv [64]byte
- copy(xprv[:], rawData[:64])
-
- xpub, err := a.wallet.ImportAccountPrivKey(xprv, in.KeyAlias, in.Password, in.Index, in.AccountAlias)
- if err != nil {
- return NewErrorResponse(err)
- }
- return NewSuccessResponse(xpub)
-}
-
-func (a *API) keyImportProgress(ctx context.Context) Response {
- data, err := a.wallet.GetRescanStatus()
- if err != nil {
- return NewErrorResponse(err)
- }
- return NewSuccessResponse(data)
-}
-
// POST /wallet error
func (a *API) walletError() Response {
return NewErrorResponse(errors.New("wallet not found, please check that the wallet is open"))
BytomcliCmd.AddCommand(deleteKeyCmd)
BytomcliCmd.AddCommand(listKeysCmd)
BytomcliCmd.AddCommand(resetKeyPwdCmd)
- BytomcliCmd.AddCommand(exportPrivateCmd)
- BytomcliCmd.AddCommand(importPrivateCmd)
- BytomcliCmd.AddCommand(importKeyProgressCmd)
BytomcliCmd.AddCommand(isMiningCmd)
package commands
import (
- "encoding/hex"
- "fmt"
"os"
- "strconv"
-
"github.com/spf13/cobra"
jww "github.com/spf13/jwalterweatherman"
- "github.com/bytom/api"
"github.com/bytom/crypto/ed25519/chainkd"
"github.com/bytom/util"
)
jww.FEEDBACK.Println("Successfully reset key password")
},
}
-
-var exportPrivateCmd = &cobra.Command{
- Use: "export-private-key <xpub> <password>",
- Short: "Export the private key",
- Args: cobra.ExactArgs(2),
- Run: func(cmd *cobra.Command, args []string) {
- type Key struct {
- Password string
- XPub chainkd.XPub
- }
- var key Key
- xpub := new(chainkd.XPub)
- rawPub, err := hex.DecodeString(args[0])
- if err != nil {
- jww.ERROR.Println("error: export-private-key args not vaild", err)
- }
- copy(xpub[:], rawPub)
-
- key.XPub = *xpub
- key.Password = args[1]
-
- data, exitCode := util.ClientCall("/export-private-key", &key)
- if exitCode != util.Success {
- os.Exit(exitCode)
- }
-
- printJSON(data)
- },
-}
-
-var importPrivateCmd = &cobra.Command{
- Use: "import-private-key <key-alias> <private key> <index> <password> <account-alias>",
- Short: "Import the private key",
- Args: cobra.ExactArgs(5),
- Run: func(cmd *cobra.Command, args []string) {
- var params api.KeyImportParams
- params.KeyAlias = args[0]
- params.XPrv = args[1]
- params.Password = args[3]
- params.AccountAlias = args[4]
- index, err := strconv.ParseUint(args[2], 10, 64)
- if err != nil {
- jww.ERROR.Println("params index wrong")
- }
- params.Index = index
-
- data, exitCode := util.ClientCall("/import-private-key", ¶ms)
- if exitCode != util.Success {
- os.Exit(exitCode)
- }
- printJSON(data)
- },
-}
-
-var importKeyProgressCmd = &cobra.Command{
- Use: "import-key-progress",
- Short: "Get import private key progress info",
- Args: cobra.NoArgs,
- Run: func(cmd *cobra.Command, args []string) {
- data, exitCode := util.ClientCall("/import-key-progress")
- if exitCode != util.Success {
- os.Exit(exitCode)
- }
- fmt.Println("data:", data)
- },
-}
return nil
}
- accounts, err := wallet.AccountMgr.ListAccounts("")
- if err != nil {
+ if _, err := wallet.AccountMgr.ListAccounts(""); err != nil {
return err
}
- if len(accounts) > 0 {
- return nil
- }
-
- for i, xPub := range xpubs {
- if err := wallet.ImportAccountXpubKey(i, xPub, w.RecoveryIndex); err != nil {
- return err
- }
- }
return nil
}
func (n *Node) MiningPool() *miningpool.MiningPool {
return n.miningPool
}
-
-//------------------------------------------------------------------------------
transactionLoop:
for pos, tx := range b.Transactions {
statusFail, _ := txStatus.GetStatus(pos)
- isLocal := false
for _, v := range tx.Outputs {
var hash [32]byte
sha3pool.Sum256(hash[:], v.ControlProgram)
if bytes := w.DB.Get(account.CPKey(hash)); bytes != nil {
- cp := &account.CtrlProgram{}
- if err := json.Unmarshal(bytes, cp); err == nil {
- w.status.OnChainAddresses.Add(cp.Address)
- }
-
annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
- isLocal = true
+ continue transactionLoop
}
}
- if isLocal {
- continue
- }
-
for _, v := range tx.Inputs {
outid, err := v.SpentOutputID()
if err != nil {
+++ /dev/null
-package wallet
-
-type AddressSet map[string]bool
-
-func NewAddressSet() AddressSet {
- return make(AddressSet)
-}
-
-// Add Add the specified element to this set if it is not already present (optional operation)
-func (s *AddressSet) Add(i string) bool {
- _, found := (*s)[i]
- if found {
- return false //False if it existed already
- }
-
- (*s)[i] = true
- return true
-}
-
-// Contains Returns true if this set contains the specified elements
-func (s *AddressSet) Contains(i ...string) bool {
- for _, val := range i {
- if _, ok := (*s)[val]; !ok {
- return false
- }
- }
- return true
-}
import (
"encoding/json"
- "fmt"
-
log "github.com/sirupsen/logrus"
- "github.com/tendermint/go-wire/data/base58"
"github.com/tendermint/tmlibs/db"
"github.com/bytom/account"
"github.com/bytom/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/types"
const RecoveryIndex = 5000
var walletKey = []byte("walletInfo")
-var dbKeyForimportingPrivateKey = []byte("importingKeysInfo")
//StatusInfo is base valid block info to handle orphan block rollback
type StatusInfo struct {
WorkHash bc.Hash
BestHeight uint64
BestHash bc.Hash
- OnChainAddresses AddressSet
-}
-
-//KeyInfo is key import status
-type KeyInfo struct {
- account account.Account
- Alias string `json:"alias"`
- XPub chainkd.XPub `json:"xpub"`
- Percent uint8 `json:"percent"`
- Complete bool `json:"complete"`
}
//Wallet is related to storing account unspent outputs
AssetReg *asset.Registry
Hsm *pseudohsm.HSM
chain *protocol.Chain
- rescanProgress chan struct{}
- ImportingPrivateKey bool
- importingKeysInfo []KeyInfo
}
//NewWallet return a new wallet instance
AssetReg: asset,
chain: chain,
Hsm: hsm,
- rescanProgress: make(chan struct{}, 1),
- importingKeysInfo: make([]KeyInfo, 0),
}
if err := w.loadWalletInfo(); err != nil {
return nil, err
}
- if err := w.loadKeysInfo(); err != nil {
- return nil, err
- }
-
- w.ImportingPrivateKey = w.getImportKeyFlag()
-
go w.walletUpdater()
return w, nil
return json.Unmarshal(rawWallet, &w.status)
}
- w.status.OnChainAddresses = NewAddressSet()
block, err := w.chain.GetBlockByHeight(0)
if err != nil {
return err
return nil
}
-//GetWalletInfo return stored wallet info and nil,if error,
-//return initial wallet info and err
-func (w *Wallet) loadKeysInfo() error {
- if rawKeyInfo := w.DB.Get(dbKeyForimportingPrivateKey); rawKeyInfo != nil {
- json.Unmarshal(rawKeyInfo, &w.importingKeysInfo)
- return nil
- }
- return nil
-}
-
-func (w *Wallet) commitkeysInfo() error {
- rawKeysInfo, err := json.Marshal(w.importingKeysInfo)
- if err != nil {
- log.WithField("err", err).Error("save wallet info")
- return err
- }
- w.DB.Set(dbKeyForimportingPrivateKey, rawKeysInfo)
- return nil
-}
-
-func (w *Wallet) getImportKeyFlag() bool {
- for _, v := range w.importingKeysInfo {
- if v.Complete == false {
- return true
- }
- }
- return false
-}
-
// AttachBlock attach a new block
func (w *Wallet) AttachBlock(block *types.Block) error {
if block.PreviousBlockHash != w.status.WorkHash {
//WalletUpdate process every valid block and reverse every invalid block which need to rollback
func (w *Wallet) walletUpdater() {
for {
- getRescanNotification(w)
- w.updateRescanStatus()
for !w.chain.InMainChain(w.status.BestHash) {
block, err := w.chain.GetBlockByHash(&w.status.BestHash)
if err != nil {
}
}
-func getRescanNotification(w *Wallet) {
- select {
- case <-w.rescanProgress:
- w.status.WorkHeight = 0
- block, _ := w.chain.GetBlockByHeight(w.status.WorkHeight)
- w.status.WorkHash = 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(xpub chainkd.XPub, auth string) (*string, error) {
- xprv, err := w.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(xprv chainkd.XPrv, keyAlias, auth string, index uint64, accountAlias string) (*pseudohsm.XPub, error) {
- if w.Hsm.HasAlias(keyAlias) {
- return nil, pseudohsm.ErrDuplicateKeyAlias
- }
- if w.Hsm.HasKey(xprv) {
- return nil, pseudohsm.ErrDuplicateKey
- }
-
- if acc, _ := w.AccountMgr.FindByAlias(nil, accountAlias); acc != nil {
- return nil, account.ErrDuplicateAlias
- }
-
- xpub, _, err := w.Hsm.ImportXPrvKey(auth, keyAlias, xprv)
- if err != nil {
- return nil, err
- }
-
- newAccount, err := w.AccountMgr.Create(nil, []chainkd.XPub{xpub.XPub}, SINGLE, accountAlias)
- if err != nil {
- w.Hsm.XDelete(xpub.XPub, auth)
- return nil, err
- }
- if err := w.recoveryAccountWalletDB(newAccount, xpub, index, keyAlias); err != nil {
- w.AccountMgr.DeleteAccount(newAccount.ID)
- w.Hsm.XDelete(xpub.XPub, auth)
- return nil, err
- }
- return xpub, nil
-}
-
-// ImportAccountXpubKey imports the account key in the Wallet Import Formt.
-func (w *Wallet) ImportAccountXpubKey(xpubIndex int, xpub pseudohsm.XPub, cpIndex uint64) error {
- accountAlias := fmt.Sprintf("recovery_%d", xpubIndex)
-
- if acc, _ := w.AccountMgr.FindByAlias(nil, accountAlias); acc != nil {
- return account.ErrDuplicateAlias
- }
-
- newAccount, err := w.AccountMgr.Create(nil, []chainkd.XPub{xpub.XPub}, SINGLE, accountAlias)
- if err != nil {
- return err
- }
-
- return w.recoveryAccountWalletDB(newAccount, &xpub, cpIndex, xpub.Alias)
-}
-
-func (w *Wallet) recoveryAccountWalletDB(account *account.Account, XPub *pseudohsm.XPub, index uint64, keyAlias string) error {
- if err := w.createProgram(account, XPub, index); err != nil {
- return err
- }
- w.ImportingPrivateKey = true
- tmp := KeyInfo{
- account: *account,
- Alias: keyAlias,
- XPub: XPub.XPub,
- Complete: false,
- }
- w.importingKeysInfo = append(w.importingKeysInfo, tmp)
- w.commitkeysInfo()
- w.rescanBlocks()
-
- return nil
-}
func (w *Wallet) createProgram(account *account.Account, XPub *pseudohsm.XPub, index uint64) error {
for i := uint64(0); i < index; i++ {
}
return nil
}
-
-func (w *Wallet) rescanBlocks() {
- select {
- case w.rescanProgress <- struct{}{}:
- default:
- return
- }
-}
-
-//GetRescanStatus return key import rescan status
-func (w *Wallet) GetRescanStatus() ([]KeyInfo, error) {
- keysInfo := make([]KeyInfo, len(w.importingKeysInfo))
-
- if rawKeyInfo := w.DB.Get(dbKeyForimportingPrivateKey); rawKeyInfo != nil {
- if err := json.Unmarshal(rawKeyInfo, &keysInfo); err != nil {
- return nil, err
- }
- }
-
- return keysInfo, nil
-}
-
-//updateRescanStatus mark private key import process `Complete` if rescan finished
-func (w *Wallet) updateRescanStatus() {
- if !w.ImportingPrivateKey {
- return
- }
-
- if w.status.WorkHeight < w.status.BestHeight {
- percent := uint8(w.status.WorkHeight * 100 / w.status.BestHeight)
- for _, keyInfo := range w.importingKeysInfo {
- keyInfo.Percent = percent
- }
- w.commitkeysInfo()
- return
- }
-
- w.ImportingPrivateKey = false
- for _, keyInfo := range w.importingKeysInfo {
- keyInfo.Percent = 100
- keyInfo.Complete = true
-
- if cps, err := w.AccountMgr.ListCtrlProgramsByAccountId(nil, keyInfo.account.ID); err == nil {
- for _, cp := range cps {
- if !w.status.OnChainAddresses.Contains(cp.Address) {
- w.AccountMgr.DeleteAccountControlProgram(cp.ControlProgram)
- }
- }
- }
- }
- w.commitkeysInfo()
-}
package wallet
import (
- "context"
"io/ioutil"
"os"
"testing"
"time"
- "github.com/tendermint/go-wire/data/base58"
dbm "github.com/tendermint/tmlibs/db"
"github.com/bytom/account"
"github.com/bytom/blockchain/txbuilder"
"github.com/bytom/consensus"
"github.com/bytom/crypto/ed25519/chainkd"
- "github.com/bytom/crypto/sha3pool"
"github.com/bytom/database/leveldb"
"github.com/bytom/protocol"
"github.com/bytom/protocol/bc"
}
}
-func TestExportAndImportPrivKey(t *testing.T) {
- dirPath, err := ioutil.TempDir(".", "")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(dirPath)
-
- testDB := dbm.NewDB("testdb", "leveldb", "temp")
- defer os.RemoveAll("temp")
-
- store := leveldb.NewStore(testDB)
- txPool := protocol.NewTxPool()
-
- chain, err := protocol.NewChain(store, txPool)
- if err != nil {
- t.Fatal(err)
- }
-
- acntManager := account.NewManager(testDB, chain)
- reg := asset.NewRegistry(testDB, chain)
-
- hsm, err := pseudohsm.New(dirPath)
- if err != nil {
- t.Fatal(err)
- }
-
- pwd := "password"
- xpub, err := hsm.XCreate("alias", pwd)
- if err != nil {
- t.Fatal(err)
- }
-
- w, err := NewWallet(testDB, acntManager, reg, hsm, chain)
- if err != nil {
- t.Fatal(err)
- }
-
- ctx := context.Background()
- acnt1, err := w.AccountMgr.Create(ctx, []chainkd.XPub{xpub.XPub}, 1, "account-alias")
- if err != nil {
- t.Fatal(err)
- }
-
- priv, err := w.ExportAccountPrivKey(xpub.XPub, pwd)
-
- wantPriv, err := hsm.LoadChainKDKey(xpub.XPub, pwd)
- if err != nil {
- t.Fatal(err)
- }
- var hashed [32]byte
- sha3pool.Sum256(hashed[:], wantPriv[:])
-
- tmp := append(wantPriv[:], hashed[:4]...)
- res := base58.Encode(tmp)
-
- if res != *priv {
- t.Fatalf("XPrivs should be identical.\nBefore: %v\n After: %v\n", *priv, res)
- }
-
- rawPriv, err := base58.Decode(*priv)
- if err != nil {
- t.Fatal(err)
- }
-
- if len(rawPriv) != 68 {
- t.Fatal("invalid private key hash length")
- }
-
- var xprv [64]byte
- copy(xprv[:], rawPriv[:64])
-
- _, err = w.ImportAccountPrivKey(xprv, xpub.Alias, pwd, 0, acnt1.Alias)
- if err != pseudohsm.ErrDuplicateKeyAlias {
- t.Fatal(err)
- }
-
- hsm.XDelete(xpub.XPub, pwd)
-
- _, err = w.ImportAccountPrivKey(xprv, xpub.Alias, pwd, 0, acnt1.Alias)
- if err != account.ErrDuplicateAlias {
- t.Fatal(err)
- }
-
- w.AccountMgr.DeleteAccount(acnt1.Alias)
-
- acnt2, err := w.ImportAccountPrivKey(xprv, xpub.Alias, pwd, 0, acnt1.Alias)
- if err != nil {
- t.Fatal(err)
- }
-
- if acnt2.XPub != acnt1.XPubs[0] {
- t.Fatalf("XPubs should be identical.\nBefore: %v\n After: %v\n", acnt1.XPubs[0], acnt2.XPub)
- }
-}
-
func mockUTXO(controlProg *account.CtrlProgram, assetID *bc.AssetID) *account.UTXO {
utxo := &account.UTXO{}
utxo.OutputID = bc.Hash{V0: 1}
AccountMgr: account,
AssetReg: asset,
chain: chain,
- rescanProgress: make(chan struct{}, 1),
- }
- wallet.status = StatusInfo{
- OnChainAddresses: NewAddressSet(),
}
return wallet
}