root 6 hónapja
commit
2141455bd9
37 módosított fájl, 4421 hozzáadás és 0 törlés
  1. 7 0
      Makefile
  2. 31 0
      conf/conf.go
  3. 5 0
      conf/conf.yaml
  4. 58 0
      crypto/crypto.go
  5. 34 0
      crypto/crypto_test.go
  6. 62 0
      crypto/scrypt.go
  7. 19 0
      crypto/scrypt_test.go
  8. 42 0
      dao/dao.go
  9. 48 0
      dao/eth.go
  10. 29 0
      dao/mnemonic.go
  11. 21 0
      dao/password.go
  12. 20 0
      dao/sign.go
  13. 42 0
      dao/tron.go
  14. 28 0
      dao/whitelist.go
  15. 61 0
      go.mod
  16. 292 0
      go.sum
  17. 523 0
      hd/extendedKey.go
  18. 120 0
      hd/hdwal.go
  19. 144 0
      hd/parsePath.go
  20. BIN
      key-manager
  21. 193 0
      key-manager.md
  22. 97 0
      key/key.go
  23. 26 0
      key/key_test.go
  24. 12 0
      key/mnemonic.go
  25. 327 0
      main.go
  26. 1 0
      pass
  27. 65 0
      proto/keyManager.proto
  28. 103 0
      readme.md
  29. 50 0
      service/const.go
  30. 171 0
      service/key.go
  31. 785 0
      service/keyManager.pb.go
  32. 249 0
      service/keyManager_grpc.pb.go
  33. 71 0
      service/mnemonic.go
  34. 98 0
      service/service.go
  35. 155 0
      service/service_test.go
  36. 282 0
      service/signer.go
  37. 150 0
      service/whitelist.go

+ 7 - 0
Makefile

@@ -0,0 +1,7 @@
+SHELL=/usr/bin/env bash
+
+.PHONY: build
+build: build
+	go mod tidy
+	rm -rf key-manager
+	go build -o key-manager main.go

+ 31 - 0
conf/conf.go

@@ -0,0 +1,31 @@
+package conf
+
+import (
+	"github.com/jinzhu/configor"
+)
+
+type mysqlSetting struct {
+	User     string `yaml:"user"`
+	Password string `yaml:"password"`
+	Host     string `yaml:"host"`
+	DB       string `yaml:"db"`
+}
+
+type Conf struct {
+	Mysql mysqlSetting `yaml:"mysql"`
+}
+
+var conf Conf
+
+func InitConfig(path string) error {
+	err := configor.Load(&conf, path)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func GetConfig() Conf {
+	return conf
+}

+ 5 - 0
conf/conf.yaml

@@ -0,0 +1,5 @@
+mysql:
+  user: lance918
+  password: Wang110@
+  host: hk-cdb-b5rihmy5.sql.tencentcdb.com:24838
+  db: key-manager

+ 58 - 0
crypto/crypto.go

@@ -0,0 +1,58 @@
+package crypto
+
+import (
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/rand"
+	"crypto/sha256"
+	"golang.org/x/crypto/scrypt"
+	"io"
+)
+
+func GenerateEncryptKey(data []byte) []byte {
+	sk, err := scrypt.Key(data, nil, 32768, 8, 1, 32)
+	if err != nil {
+		panic(err)
+	}
+
+	sum := sha256.Sum256(append(data, sk...))
+	return sum[:]
+}
+
+func Hash256(data []byte) []byte {
+	sum := sha256.Sum256(data)
+	return sum[:]
+}
+
+func Encrypt(data []byte, key []byte) ([]byte, error) {
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		panic(err)
+	}
+
+	ciphertext := make([]byte, aes.BlockSize+len(data))
+	iv := ciphertext[:aes.BlockSize]
+	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
+		panic(err)
+	}
+
+	stream := cipher.NewCTR(block, iv)
+	stream.XORKeyStream(ciphertext[aes.BlockSize:], data)
+
+	return ciphertext, nil
+}
+
+func Decrypt(encryptedData []byte, key []byte) ([]byte, error) {
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		panic(err)
+	}
+
+	data := make([]byte, len(encryptedData[aes.BlockSize:]))
+
+	iv := encryptedData[:aes.BlockSize]
+	stream := cipher.NewCTR(block, iv)
+	stream.XORKeyStream(data, encryptedData[aes.BlockSize:])
+
+	return data, nil
+}

+ 34 - 0
crypto/crypto_test.go

@@ -0,0 +1,34 @@
+package crypto
+
+import (
+	"encoding/hex"
+	"github.com/stretchr/testify/require"
+	"testing"
+)
+
+func TestGenerateEncryptKey(t *testing.T) {
+	key1 := GenerateEncryptKey([]byte("hello world"))
+	key2 := GenerateEncryptKey([]byte("hello world"))
+	require.Equal(t, key1, key2)
+}
+
+func TestEncryptAndDecrypt(t *testing.T) {
+	key := Hash256([]byte("hello world"))
+
+	t.Log(hex.EncodeToString(key))
+
+	data := "OpenFilWallet Encrypt"
+	encryptedData, err := Encrypt([]byte(data), key)
+	if err != nil {
+		t.Fatal(err)
+	}
+	t.Log(hex.EncodeToString(encryptedData))
+
+	decryptData, err := Decrypt(encryptedData, key)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	require.Equal(t, data, string(decryptData))
+
+}

+ 62 - 0
crypto/scrypt.go

@@ -0,0 +1,62 @@
+package crypto
+
+import (
+	"crypto/rand"
+	"crypto/subtle"
+	"errors"
+	"golang.org/x/crypto/scrypt"
+	"io"
+)
+
+const (
+	// the salt is a random number of length 8
+	saltSize = 8
+	// scrypt key = salt + scrypt, length:40 = 8 + 32
+	scryptKeySize = 40
+)
+
+var (
+	ErrInvalidLength      = errors.New("invalid scrypt key length")
+	ErrMismatchedPassword = errors.New("password does not match scrypt key")
+)
+
+func Scrypt(password string) []byte {
+	salt := make([]byte, saltSize)
+	if _, err := io.ReadFull(rand.Reader, salt); err != nil {
+		panic(err)
+	}
+
+	// The recommended parameters for interactive logins as of 2017 are N=32768, r=8 and p=1.
+	sk, err := scrypt.Key([]byte(password), salt, 32768, 8, 1, 32)
+	if err != nil {
+		panic(err)
+	}
+
+	key := append(salt, sk...)
+
+	if len(key) != scryptKeySize {
+		panic("invalid length")
+	}
+
+	return key
+}
+
+func VerifyScrypt(password string, scryptKey []byte) (bool, error) {
+	if len(scryptKey) != scryptKeySize {
+		return false, ErrInvalidLength
+	}
+
+	salt := scryptKey[:saltSize]
+
+	// The recommended parameters for interactive logins as of 2017 are N=32768, r=8 and p=1.
+	sk, err := scrypt.Key([]byte(password), salt, 32768, 8, 1, 32)
+	if err != nil {
+		panic(err)
+	}
+
+	if subtle.ConstantTimeCompare(sk, scryptKey[saltSize:]) == 1 {
+		return true, nil
+	}
+
+	return false, ErrMismatchedPassword
+}

+ 19 - 0
crypto/scrypt_test.go

@@ -0,0 +1,19 @@
+package crypto
+
+import (
+	"encoding/hex"
+	"github.com/stretchr/testify/require"
+	"testing"
+)
+
+func TestScrypt(t *testing.T) {
+	key := Scrypt("hello world")
+	t.Log(hex.EncodeToString(key))
+
+	ok, err := VerifyScrypt("hello world", key)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	require.True(t, ok)
+}

+ 42 - 0
dao/dao.go

@@ -0,0 +1,42 @@
+package dao
+
+import (
+	"fmt"
+	"gorm.io/driver/mysql"
+	"gorm.io/gorm"
+	"gorm.io/gorm/logger"
+	"key-manager/conf"
+)
+
+type Dao struct {
+	db *gorm.DB
+}
+
+func InitMysqlDB() (*Dao, error) {
+	conf := conf.GetConfig()
+	dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local",
+		conf.Mysql.User,
+		conf.Mysql.Password,
+		conf.Mysql.Host,
+		conf.Mysql.DB,
+	)
+
+	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
+		//Logger: logger.Default.LogMode(logger.Info),
+		Logger: logger.Default.LogMode(logger.Silent),
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	db.AutoMigrate(&EthAddressInfo{})
+	db.AutoMigrate(&TronAddressInfo{})
+	db.AutoMigrate(&Mnemonic{})
+	db.AutoMigrate(&Password{})
+	db.AutoMigrate(&Whitelist{})
+	db.AutoMigrate(&SignTx{})
+
+	return &Dao{
+		db: db,
+	}, nil
+}

+ 48 - 0
dao/eth.go

@@ -0,0 +1,48 @@
+package dao
+
+import (
+	"gorm.io/gorm"
+)
+
+type EthAddressInfo struct {
+	gorm.Model
+	Name       string `gorm:"column:name" json:"name"`
+	EthIndex   int64  `gorm:"column:eth_index" json:"eth_index"`
+	EthAddress string `gorm:"type:varchar(200);column:eth_address;uniqueIndex" json:"eth_address"`
+}
+
+type AccountInfo interface {
+	GetName() string
+	GetIndex() int64
+	GetAddr() string
+}
+
+func (eth *EthAddressInfo) GetName() string {
+	return eth.Name
+}
+func (eth *EthAddressInfo) GetIndex() int64 {
+	return eth.EthIndex
+}
+func (eth *EthAddressInfo) GetAddr() string {
+	return eth.EthAddress
+}
+
+func (eth *EthAddressInfo) TableName() string {
+	return "key_manager_eth"
+}
+
+func (dao *Dao) CreateEth(data *EthAddressInfo) error {
+	return dao.db.Create(&data).Error
+}
+
+func (dao *Dao) GetEthFromAddress(addr string) (*EthAddressInfo, error) {
+	var ethAddressInfo EthAddressInfo
+	err := dao.db.Table(new(EthAddressInfo).TableName()).Where("eth_address=?", addr).First(&ethAddressInfo).Error
+	return &ethAddressInfo, err
+}
+
+func (dao *Dao) GetEthFromIndex(name string, index int64) (*EthAddressInfo, error) {
+	var ethAddressInfo EthAddressInfo
+	err := dao.db.Table(new(EthAddressInfo).TableName()).Where("name=? AND eth_index=?", name, index).First(&ethAddressInfo).Error
+	return &ethAddressInfo, err
+}

+ 29 - 0
dao/mnemonic.go

@@ -0,0 +1,29 @@
+package dao
+
+import "gorm.io/gorm"
+
+type Mnemonic struct {
+	gorm.Model
+	Name     string `gorm:"type:varchar(200);column:name;uniqueIndex" json:"name"`
+	Mnemonic string `gorm:"column:mnemonic" json:"mnemonic"`
+}
+
+func (m *Mnemonic) TableName() string {
+	return "key_manager_mnemonic"
+}
+
+func (dao *Dao) CreateMnemonic(data *Mnemonic) error {
+	return dao.db.Create(&data).Error
+}
+
+func (dao *Dao) GetMnemonic(name string) (*Mnemonic, error) {
+	var mnemonic Mnemonic
+	err := dao.db.Table(new(Mnemonic).TableName()).Where("name=?", name).First(&mnemonic).Error
+	return &mnemonic, err
+}
+
+func (dao *Dao) GetAllMnemonics() ([]Mnemonic, error) {
+	var mnemonics []Mnemonic
+	err := dao.db.Table(new(Mnemonic).TableName()).Find(&mnemonics).Error
+	return mnemonics, err
+}

+ 21 - 0
dao/password.go

@@ -0,0 +1,21 @@
+package dao
+
+import "gorm.io/gorm"
+
+type Password struct {
+	gorm.Model
+	Password string `gorm:"column:password" json:"password"`
+}
+
+func (m *Password) TableName() string {
+	return "key_manager_password"
+}
+
+func (dao *Dao) CreatePassword(data *Password) error {
+	return dao.db.Create(&data).Error
+}
+func (dao *Dao) GetPassword() (string, error) {
+	var p Password
+	err := dao.db.Table(new(Password).TableName()).First(&p).Error
+	return p.Password, err
+}

+ 20 - 0
dao/sign.go

@@ -0,0 +1,20 @@
+package dao
+
+import "gorm.io/gorm"
+
+type SignTx struct {
+	gorm.Model
+	Network string `gorm:"column:network" json:"network"`
+	Coin    string `gorm:"column:coin" json:"coin"`
+	From    string `gorm:"column:from" json:"from"`
+	To      string `gorm:"column:to" json:"to"`
+	Amount  string `gorm:"column:amount" json:"amount"`
+}
+
+func (m *SignTx) TableName() string {
+	return "key_manager_sign_tx"
+}
+
+func (dao *Dao) CreateSignTx(data *SignTx) error {
+	return dao.db.Create(&data).Error
+}

+ 42 - 0
dao/tron.go

@@ -0,0 +1,42 @@
+package dao
+
+import (
+	"gorm.io/gorm"
+)
+
+type TronAddressInfo struct {
+	gorm.Model
+	Name        string `gorm:"column:name" json:"name"`
+	TronIndex   int64  `gorm:"column:tron_index" json:"tron_index"`
+	TronAddress string `gorm:"type:varchar(200);column:tron_address;uniqueIndex" json:"tron_address"`
+}
+
+func (tron *TronAddressInfo) GetName() string {
+	return tron.Name
+}
+func (tron *TronAddressInfo) GetIndex() int64 {
+	return tron.TronIndex
+}
+func (tron *TronAddressInfo) GetAddr() string {
+	return tron.TronAddress
+}
+
+func (tron *TronAddressInfo) TableName() string {
+	return "key_manager_tron"
+}
+
+func (dao *Dao) CreateTron(data *TronAddressInfo) error {
+	return dao.db.Create(&data).Error
+}
+
+func (dao *Dao) GetTronFromAddress(addr string) (*TronAddressInfo, error) {
+	var tronAddressInfo TronAddressInfo
+	err := dao.db.Table(new(TronAddressInfo).TableName()).Where("tron_address=?", addr).First(&tronAddressInfo).Error
+	return &tronAddressInfo, err
+}
+
+func (dao *Dao) GetTronFromIndex(name string, index int64) (*TronAddressInfo, error) {
+	var tronAddressInfo TronAddressInfo
+	err := dao.db.Table(new(TronAddressInfo).TableName()).Where("name=? AND tron_index=?", name, index).First(&tronAddressInfo).Error
+	return &tronAddressInfo, err
+}

+ 28 - 0
dao/whitelist.go

@@ -0,0 +1,28 @@
+package dao
+
+import "gorm.io/gorm"
+
+type Whitelist struct {
+	gorm.Model
+	Name         string `gorm:"type:varchar(200);column:name;uniqueIndex" json:"name"`
+	WhiteAddress string `gorm:"type:varchar(200);column:white_address;uniqueIndex" json:"white_address"`
+}
+
+func (w *Whitelist) TableName() string {
+	return "key_manager_whitelist"
+}
+
+func (dao *Dao) CreateWhitelist(data *Whitelist) error {
+	return dao.db.Create(&data).Error
+}
+
+func (dao *Dao) DeleteWhitelist(name string) error {
+	err := dao.db.Where("name=?", name).Delete(&Whitelist{}).Error
+	return err
+}
+
+func (dao *Dao) GetAllWhitelists() ([]Whitelist, error) {
+	var whitelists []Whitelist
+	err := dao.db.Table(new(Whitelist).TableName()).Find(&whitelists).Error
+	return whitelists, err
+}

+ 61 - 0
go.mod

@@ -0,0 +1,61 @@
+module key-manager
+
+go 1.19
+
+require (
+	github.com/btcsuite/btcd v0.22.1
+	github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1
+	github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce
+	github.com/ethereum/go-ethereum v1.10.26
+	github.com/fbsobreira/gotron-sdk v0.0.0-20230714102740-d3204bd08259
+	github.com/ipfs/go-log/v2 v2.5.1
+	github.com/jinzhu/configor v1.2.1
+	github.com/shirou/gopsutil v3.21.11+incompatible
+	github.com/stretchr/testify v1.8.4
+	github.com/tyler-smith/go-bip39 v1.1.0
+	github.com/urfave/cli/v2 v2.10.2
+	golang.org/x/crypto v0.7.0
+	google.golang.org/grpc v1.47.0
+	google.golang.org/protobuf v1.28.1
+	gorm.io/driver/mysql v1.5.1
+	gorm.io/gorm v1.25.3
+)
+
+require (
+	github.com/BurntSushi/toml v1.1.0 // indirect
+	github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
+	github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
+	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/deckarep/golang-set v1.8.0 // indirect
+	github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
+	github.com/go-ole/go-ole v1.2.6 // indirect
+	github.com/go-sql-driver/mysql v1.7.0 // indirect
+	github.com/go-stack/stack v1.8.0 // indirect
+	github.com/golang/protobuf v1.5.2 // indirect
+	github.com/google/uuid v1.2.0 // indirect
+	github.com/jinzhu/inflection v1.0.0 // indirect
+	github.com/jinzhu/now v1.1.5 // indirect
+	github.com/mattn/go-isatty v0.0.14 // indirect
+	github.com/mattn/go-runewidth v0.0.9 // indirect
+	github.com/mitchellh/go-homedir v1.1.0 // indirect
+	github.com/pborman/uuid v1.2.1 // indirect
+	github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 // indirect
+	github.com/pkg/errors v0.9.1 // indirect
+	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/rjeczalik/notify v0.9.3 // indirect
+	github.com/russross/blackfriday/v2 v2.1.0 // indirect
+	github.com/shengdoushi/base58 v1.0.0 // indirect
+	github.com/tklauser/go-sysconf v0.3.12 // indirect
+	github.com/tklauser/numcpus v0.6.1 // indirect
+	github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
+	github.com/yusufpapurcu/wmi v1.2.3 // indirect
+	go.uber.org/atomic v1.7.0 // indirect
+	go.uber.org/multierr v1.6.0 // indirect
+	go.uber.org/zap v1.19.1 // indirect
+	golang.org/x/net v0.8.0 // indirect
+	golang.org/x/sys v0.11.0 // indirect
+	golang.org/x/text v0.8.0 // indirect
+	google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 // indirect
+	gopkg.in/yaml.v2 v2.4.0 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)

+ 292 - 0
go.sum

@@ -0,0 +1,292 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
+github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o=
+github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
+github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
+github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c=
+github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y=
+github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k=
+github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU=
+github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
+github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
+github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
+github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
+github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ=
+github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o=
+github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
+github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
+github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
+github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
+github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
+github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
+github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4=
+github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo=
+github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
+github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/ethereum/go-ethereum v1.10.26 h1:i/7d9RBBwiXCEuyduBQzJw/mKmnvzsN14jqBmytw72s=
+github.com/ethereum/go-ethereum v1.10.26/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg=
+github.com/fbsobreira/gotron-sdk v0.0.0-20230714102740-d3204bd08259 h1:HAcHwvPamxsjiPkU6TtFsHbYYdauhJ1BnDt631nPZvI=
+github.com/fbsobreira/gotron-sdk v0.0.0-20230714102740-d3204bd08259/go.mod h1:Sj3nZuicr/3RoekvShKtFRwmYVDSOE/X1gLez8f+7ps=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
+github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
+github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
+github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY=
+github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI=
+github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/jinzhu/configor v1.2.1 h1:OKk9dsR8i6HPOCZR8BcMtcEImAFjIhbJFZNyn5GCZko=
+github.com/jinzhu/configor v1.2.1/go.mod h1:nX89/MOmDba7ZX7GCyU/VIaQ2Ar2aizBl2d3JLF/rDc=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
+github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
+github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
+github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=
+github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
+github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM=
+github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA=
+github.com/rjeczalik/notify v0.9.3 h1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY=
+github.com/rjeczalik/notify v0.9.3/go.mod h1:gF3zSOrafR9DQEWSE8TjfI9NkooDxbyT4UgRGKZA0lc=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
+github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/shengdoushi/base58 v1.0.0 h1:tGe4o6TmdXFJWoI31VoSWvuaKxf0Px3gqa3sUWhAxBs=
+github.com/shengdoushi/base58 v1.0.0/go.mod h1:m5uIILfzcKMw6238iWAhP4l3s5+uXyF3+bJKUNhAL9I=
+github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
+github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
+github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
+github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
+github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
+github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
+github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8=
+github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U=
+github.com/urfave/cli/v2 v2.10.2 h1:x3p8awjp/2arX+Nl/G2040AZpOCHS/eMJJ1/a+mye4Y=
+github.com/urfave/cli/v2 v2.10.2/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo=
+github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
+github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
+github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/goleak v1.1.11-0.20210813005559-691160354723 h1:sHOAIxRGBp443oHZIPB+HsUGaksVCXVQENPxwTfQdH4=
+go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI=
+go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
+golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
+golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
+golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
+golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
+golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8=
+google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8=
+google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
+google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gorm.io/driver/mysql v1.5.1 h1:WUEH5VF9obL/lTtzjmML/5e6VfFR/788coz2uaVCAZw=
+gorm.io/driver/mysql v1.5.1/go.mod h1:Jo3Xu7mMhCyj8dlrb3WoCaRd1FhsVh+yMXb1jUInf5o=
+gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
+gorm.io/gorm v1.25.3 h1:zi4rHZj1anhZS2EuEODMhDisGy+Daq9jtPrNGgbQYD8=
+gorm.io/gorm v1.25.3/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

+ 523 - 0
hd/extendedKey.go

@@ -0,0 +1,523 @@
+package hd
+
+// References:
+//   [BIP32]: BIP0032 - Hierarchical Deterministic Wallets
+//   https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
+
+import (
+	"bytes"
+	"crypto/hmac"
+	"crypto/sha512"
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"math/big"
+
+	"github.com/btcsuite/btcd/btcec"
+	"github.com/btcsuite/btcd/chaincfg"
+	"github.com/btcsuite/btcd/chaincfg/chainhash"
+	"github.com/btcsuite/btcutil"
+	"github.com/btcsuite/btcutil/base58"
+)
+
+const (
+	// RecommendedSeedLen is the recommended length in bytes for a seed
+	// to a master node.
+	RecommendedSeedLen = 32 // 256 bits
+
+	// HardenedKeyStart is the index at which a hardended key starts.  Each
+	// extended key has 2^31 normal child keys and 2^31 hardned child keys.
+	// Thus the range for normal child keys is [0, 2^31 - 1] and the range
+	// for hardened child keys is [2^31, 2^32 - 1].
+	HardenedKeyStart = 0x80000000 // 2^31
+
+	// MinSeedBytes is the minimum number of bytes allowed for a seed to
+	// a master node.
+	MinSeedBytes = 16 // 128 bits
+
+	// MaxSeedBytes is the maximum number of bytes allowed for a seed to
+	// a master node.
+	MaxSeedBytes = 64 // 512 bits
+
+	// serializedKeyLen is the length of a serialized public or private
+	// extended key.  It consists of 4 bytes version, 1 byte depth, 4 bytes
+	// fingerprint, 4 bytes child number, 32 bytes chain code, and 33 bytes
+	// public/private key data.
+	serializedKeyLen = 4 + 1 + 4 + 4 + 32 + 33 // 78 bytes
+
+	// maxUint8 is the max positive integer which can be serialized in a uint8
+	maxUint8 = 1<<8 - 1
+)
+
+var (
+	// ErrDeriveHardFromPublic describes an error in which the caller
+	// attempted to derive a hardened extended key from a public key.
+	ErrDeriveHardFromPublic = errors.New("cannot derive a hardened key " +
+		"from a public key")
+
+	// ErrDeriveBeyondMaxDepth describes an error in which the caller
+	// has attempted to derive more than 255 keys from a root key.
+	ErrDeriveBeyondMaxDepth = errors.New("cannot derive a key with more than " +
+		"255 indices in its path")
+
+	// ErrNotPrivExtKey describes an error in which the caller attempted
+	// to extract a private key from a public extended key.
+	ErrNotPrivExtKey = errors.New("unable to create private keys from a " +
+		"public extended key")
+
+	// ErrInvalidChild describes an error in which the child at a specific
+	// index is invalid due to the derived key falling outside of the valid
+	// range for secp256k1 private keys.  This error indicates the caller
+	// should simply ignore the invalid child extended key at this index and
+	// increment to the next index.
+	ErrInvalidChild = errors.New("the extended key at this index is invalid")
+
+	// ErrUnusableSeed describes an error in which the provided seed is not
+	// usable due to the derived key falling outside of the valid range for
+	// secp256k1 private keys.  This error indicates the caller must choose
+	// another seed.
+	ErrUnusableSeed = errors.New("unusable seed")
+
+	// ErrInvalidSeedLen describes an error in which the provided seed or
+	// seed length is not in the allowed range.
+	ErrInvalidSeedLen = fmt.Errorf("seed length must be between %d and %d "+
+		"bits", MinSeedBytes*8, MaxSeedBytes*8)
+
+	// ErrBadChecksum describes an error in which the checksum encoded with
+	// a serialized extended key does not match the calculated value.
+	ErrBadChecksum = errors.New("bad extended key checksum")
+
+	// ErrInvalidKeyLen describes an error in which the provided serialized
+	// key is not the expected length.
+	ErrInvalidKeyLen = errors.New("the provided serialized extended key " +
+		"length is invalid")
+)
+
+// masterKey is the master key used along with a random seed used to generate
+// the master node in the hierarchical tree.
+var masterKey = []byte("Bitcoin seed")
+
+// ExtendedKey houses all the information needed to support a hierarchical
+// deterministic extended key.  See the package overview documentation for
+// more details on how to use extended keys.
+type ExtendedKey struct {
+	key       []byte // This will be the pubkey for extended pub keys
+	pubKey    []byte // This will only be set for extended priv keys
+	chainCode []byte
+	depth     uint8
+	parentFP  []byte
+	childNum  uint32
+	isPrivate bool
+}
+
+// NewExtendedKey returns a new instance of an extended key with the given
+// fields.  No error checking is performed here as it's only intended to be a
+// convenience method used to create a populated struct. This function should
+// only by used by applications that need to create custom ExtendedKeys. All
+// other applications should just use NewMaster, Child, or Neuter.
+func NewExtendedKey(key, chainCode, parentFP []byte, depth uint8,
+	childNum uint32, isPrivate bool) *ExtendedKey {
+
+	// NOTE: The pubKey field is intentionally left nil so it is only
+	// computed and memoized as required.
+	return &ExtendedKey{
+		key:       key,
+		chainCode: chainCode,
+		depth:     depth,
+		parentFP:  parentFP,
+		childNum:  childNum,
+		isPrivate: isPrivate,
+	}
+}
+
+// pubKeyBytes returns bytes for the serialized compressed public key associated
+// with this extended key in an efficient manner including memoization as
+// necessary.
+//
+// When the extended key is already a public key, the key is simply returned as
+// is since it's already in the correct form.  However, when the extended key is
+// a private key, the public key will be calculated and memoized so future
+// accesses can simply return the cached result.
+func (k *ExtendedKey) pubKeyBytes() []byte {
+	// Just return the key if it's already an extended public key.
+	if !k.isPrivate {
+		return k.key
+	}
+
+	// This is a private extended key, so calculate and memoize the public
+	// key if needed.
+	if len(k.pubKey) == 0 {
+		pkx, pky := btcec.S256().ScalarBaseMult(k.key)
+		pubKey := btcec.PublicKey{Curve: btcec.S256(), X: pkx, Y: pky}
+		k.pubKey = pubKey.SerializeCompressed()
+	}
+
+	return k.pubKey
+}
+
+// IsPrivate returns whether or not the extended key is a private extended key.
+//
+// A private extended key can be used to derive both hardened and non-hardened
+// child private and public extended keys.  A public extended key can only be
+// used to derive non-hardened child public extended keys.
+func (k *ExtendedKey) IsPrivate() bool {
+	return k.isPrivate
+}
+
+// Depth returns the current derivation level with respect to the root.
+//
+// The root key has depth zero, and the field has a maximum of 255 due to
+// how depth is serialized.
+func (k *ExtendedKey) Depth() uint8 {
+	return k.depth
+}
+
+// ParentFingerprint returns a fingerprint of the parent extended key from which
+// this one was derived.
+func (k *ExtendedKey) ParentFingerprint() uint32 {
+	return binary.BigEndian.Uint32(k.parentFP)
+}
+
+// Child returns a derived child extended key at the given index.  When this
+// extended key is a private extended key (as determined by the IsPrivate
+// function), a private extended key will be derived.  Otherwise, the derived
+// extended key will be also be a public extended key.
+//
+// When the index is greater to or equal than the HardenedKeyStart constant, the
+// derived extended key will be a hardened extended key.  It is only possible to
+// derive a hardended extended key from a private extended key.  Consequently,
+// this function will return ErrDeriveHardFromPublic if a hardened child
+// extended key is requested from a public extended key.
+//
+// A hardened extended key is useful since, as previously mentioned, it requires
+// a parent private extended key to derive.  In other words, normal child
+// extended public keys can be derived from a parent public extended key (no
+// knowledge of the parent private key) whereas hardened extended keys may not
+// be.
+//
+// NOTE: There is an extremely small chance (< 1 in 2^127) the specific child
+// index does not derive to a usable child.  The ErrInvalidChild error will be
+// returned if this should occur, and the caller is expected to ignore the
+// invalid child and simply increment to the next index.
+func (k *ExtendedKey) Child(i uint32) (*ExtendedKey, error) {
+	// Prevent derivation of children beyond the max allowed depth.
+	if k.depth == maxUint8 {
+		return nil, ErrDeriveBeyondMaxDepth
+	}
+
+	// There are four scenarios that could happen here:
+	// 1) Private extended key -> Hardened child private extended key
+	// 2) Private extended key -> Non-hardened child private extended key
+	// 3) Public extended key -> Non-hardened child public extended key
+	// 4) Public extended key -> Hardened child public extended key (INVALID!)
+
+	// Case #4 is invalid, so error out early.
+	// A hardened child extended key may not be created from a public
+	// extended key.
+	isChildHardened := i >= HardenedKeyStart
+	if !k.isPrivate && isChildHardened {
+		return nil, ErrDeriveHardFromPublic
+	}
+
+	// The data used to derive the child key depends on whether or not the
+	// child is hardened per [BIP32].
+	//
+	// For hardened children:
+	//   0x00 || ser256(parentKey) || ser32(i)
+	//
+	// For normal children:
+	//   serP(parentPubKey) || ser32(i)
+	keyLen := 33
+	data := make([]byte, keyLen+4)
+	if isChildHardened {
+		// Case #1.
+		// When the child is a hardened child, the key is known to be a
+		// private key due to the above early return.  Pad it with a
+		// leading zero as required by [BIP32] for deriving the child.
+		copy(data[1:], k.key)
+	} else {
+		// Case #2 or #3.
+		// This is either a public or private extended key, but in
+		// either case, the data which is used to derive the child key
+		// starts with the secp256k1 compressed public key bytes.
+		copy(data, k.pubKeyBytes())
+	}
+	binary.BigEndian.PutUint32(data[keyLen:], i)
+
+	// Take the HMAC-SHA512 of the current key's chain code and the derived
+	// data:
+	//   I = HMAC-SHA512(Key = chainCode, Data = data)
+	hmac512 := hmac.New(sha512.New, k.chainCode)
+	hmac512.Write(data)
+	ilr := hmac512.Sum(nil)
+
+	// Split "I" into two 32-byte sequences Il and Ir where:
+	//   Il = intermediate key used to derive the child
+	//   Ir = child chain code
+	il := ilr[:len(ilr)/2]
+	childChainCode := ilr[len(ilr)/2:]
+
+	// Both derived public or private keys rely on treating the left 32-byte
+	// sequence calculated above (Il) as a 256-bit integer that must be
+	// within the valid range for a secp256k1 private key.  There is a small
+	// chance (< 1 in 2^127) this condition will not hold, and in that case,
+	// a child extended key can't be created for this index and the caller
+	// should simply increment to the next index.
+	ilNum := new(big.Int).SetBytes(il)
+	if ilNum.Cmp(btcec.S256().N) >= 0 || ilNum.Sign() == 0 {
+		return nil, ErrInvalidChild
+	}
+
+	// The algorithm used to derive the child key depends on whether or not
+	// a private or public child is being derived.
+	//
+	// For private children:
+	//   childKey = parse256(Il) + parentKey
+	//
+	// For public children:
+	//   childKey = serP(point(parse256(Il)) + parentKey)
+	var isPrivate bool
+	var childKey []byte
+	if k.isPrivate {
+		// Case #1 or #2.
+		// Add the parent private key to the intermediate private key to
+		// derive the final child key.
+		//
+		// childKey = parse256(Il) + parenKey
+		keyNum := new(big.Int).SetBytes(k.key)
+		ilNum.Add(ilNum, keyNum)
+		ilNum.Mod(ilNum, btcec.S256().N)
+		childKey = ilNum.Bytes()
+		isPrivate = true
+	} else {
+		// Case #3.
+		// Calculate the corresponding intermediate public key for
+		// intermediate private key.
+		ilx, ily := btcec.S256().ScalarBaseMult(il)
+		if ilx.Sign() == 0 || ily.Sign() == 0 {
+			return nil, ErrInvalidChild
+		}
+
+		// Convert the serialized compressed parent public key into X
+		// and Y coordinates so it can be added to the intermediate
+		// public key.
+		pubKey, err := btcec.ParsePubKey(k.key, btcec.S256())
+		if err != nil {
+			return nil, err
+		}
+
+		// Add the intermediate public key to the parent public key to
+		// derive the final child key.
+		//
+		// childKey = serP(point(parse256(Il)) + parentKey)
+		childX, childY := btcec.S256().Add(ilx, ily, pubKey.X, pubKey.Y)
+		pk := btcec.PublicKey{Curve: btcec.S256(), X: childX, Y: childY}
+		childKey = pk.SerializeCompressed()
+	}
+
+	// The fingerprint of the parent for the derived child is the first 4
+	// bytes of the RIPEMD160(SHA256(parentPubKey)).
+	parentFP := btcutil.Hash160(k.pubKeyBytes())[:4]
+	return NewExtendedKey(childKey, childChainCode, parentFP,
+		k.depth+1, i, isPrivate), nil
+}
+
+// Neuter returns a new extended public key from this extended private key.  The
+// same extended key will be returned unaltered if it is already an extended
+// public key.
+//
+// As the name implies, an extended public key does not have access to the
+// private key, so it is not capable of signing transactions or deriving
+// child extended private keys.  However, it is capable of deriving further
+// child extended public keys.
+func (k *ExtendedKey) Neuter() (*ExtendedKey, error) {
+	// Already an extended public key.
+	if !k.isPrivate {
+		return k, nil
+	}
+
+	// Convert it to an extended public key.  The key for the new extended
+	// key will simply be the pubkey of the current extended private key.
+	//
+	// This is the function N((k,c)) -> (K, c) from [BIP32].
+	return NewExtendedKey(k.pubKeyBytes(), k.chainCode, k.parentFP,
+		k.depth, k.childNum, false), nil
+}
+
+// ECPubKey converts the extended key to a btcec public key and returns it.
+func (k *ExtendedKey) ECPubKey() (*btcec.PublicKey, error) {
+	return btcec.ParsePubKey(k.pubKeyBytes(), btcec.S256())
+}
+
+// ECPrivKey converts the extended key to a btcec private key and returns it.
+// As you might imagine this is only possible if the extended key is a private
+// extended key (as determined by the IsPrivate function).  The ErrNotPrivExtKey
+// error will be returned if this function is called on a public extended key.
+func (k *ExtendedKey) ECPrivKey() (*btcec.PrivateKey, error) {
+	if !k.isPrivate {
+		return nil, ErrNotPrivExtKey
+	}
+
+	privKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), k.key)
+	return privKey, nil
+}
+
+// Address converts the extended key to a standard bitcoin pay-to-pubkey-hash
+// address for the passed network.
+func (k *ExtendedKey) Address(net *chaincfg.Params) (*btcutil.AddressPubKeyHash, error) {
+	pkHash := btcutil.Hash160(k.pubKeyBytes())
+	return btcutil.NewAddressPubKeyHash(pkHash, net)
+}
+
+// paddedAppend appends the src byte slice to dst, returning the new slice.
+// If the length of the source is smaller than the passed size, leading zero
+// bytes are appended to the dst slice before appending src.
+func paddedAppend(size uint, dst, src []byte) []byte {
+	for i := 0; i < int(size)-len(src); i++ {
+		dst = append(dst, 0)
+	}
+	return append(dst, src...)
+}
+
+// String returns the extended key as a human-readable base58-encoded string.
+func (k *ExtendedKey) String() string {
+	if len(k.key) == 0 {
+		return "zeroed extended key"
+	}
+
+	var childNumBytes [4]byte
+	binary.BigEndian.PutUint32(childNumBytes[:], k.childNum)
+
+	// The serialized format is:
+	//   version (4) || depth (1) || parent fingerprint (4)) ||
+	//   child num (4) || chain code (32) || key data (33) || checksum (4)
+	serializedBytes := make([]byte, 0, serializedKeyLen+4)
+	serializedBytes = append(serializedBytes, k.depth)
+	serializedBytes = append(serializedBytes, k.parentFP...)
+	serializedBytes = append(serializedBytes, childNumBytes[:]...)
+	serializedBytes = append(serializedBytes, k.chainCode...)
+	if k.isPrivate {
+		serializedBytes = append(serializedBytes, 0x00)
+		serializedBytes = paddedAppend(32, serializedBytes, k.key)
+	} else {
+		serializedBytes = append(serializedBytes, k.pubKeyBytes()...)
+	}
+
+	checkSum := chainhash.DoubleHashB(serializedBytes)[:4]
+	serializedBytes = append(serializedBytes, checkSum...)
+	return base58.Encode(serializedBytes)
+}
+
+// zero sets all bytes in the passed slice to zero.  This is used to
+// explicitly clear private key material from memory.
+func zero(b []byte) {
+	lenb := len(b)
+	for i := 0; i < lenb; i++ {
+		b[i] = 0
+	}
+}
+
+// Zero manually clears all fields and bytes in the extended key.  This can be
+// used to explicitly clear key material from memory for enhanced security
+// against memory scraping.  This function only clears this particular key and
+// not any children that have already been derived.
+func (k *ExtendedKey) Zero() {
+	zero(k.key)
+	zero(k.pubKey)
+	zero(k.chainCode)
+	zero(k.parentFP)
+	k.key = nil
+	k.depth = 0
+	k.childNum = 0
+	k.isPrivate = false
+}
+
+// NewMaster creates a new master node for use in creating a hierarchical
+// deterministic key chain.  The seed must be between 128 and 512 bits and
+// should be generated by a cryptographically secure random generation source.
+//
+// NOTE: There is an extremely small chance (< 1 in 2^127) the provided seed
+// will derive to an unusable secret key.  The ErrUnusable error will be
+// returned if this should occur, so the caller must check for it and generate a
+// new seed accordingly.
+func NewMaster(seed []byte) (*ExtendedKey, error) {
+	// Per [BIP32], the seed must be in range [MinSeedBytes, MaxSeedBytes].
+	if len(seed) < MinSeedBytes || len(seed) > MaxSeedBytes {
+		return nil, ErrInvalidSeedLen
+	}
+
+	// First take the HMAC-SHA512 of the master key and the seed data:
+	//   I = HMAC-SHA512(Key = "Bitcoin seed", Data = S)
+	hmac512 := hmac.New(sha512.New, masterKey)
+	hmac512.Write(seed)
+	lr := hmac512.Sum(nil)
+
+	// Split "I" into two 32-byte sequences Il and Ir where:
+	//   Il = master secret key
+	//   Ir = master chain code
+	secretKey := lr[:len(lr)/2]
+	chainCode := lr[len(lr)/2:]
+
+	// Ensure the key in usable.
+	secretKeyNum := new(big.Int).SetBytes(secretKey)
+	if secretKeyNum.Cmp(btcec.S256().N) >= 0 || secretKeyNum.Sign() == 0 {
+		return nil, ErrUnusableSeed
+	}
+
+	parentFP := []byte{0x00, 0x00, 0x00, 0x00}
+	return NewExtendedKey(secretKey, chainCode,
+		parentFP, 0, 0, true), nil
+}
+
+// NewKeyFromString returns a new extended key instance from a base58-encoded
+// extended key.
+func NewKeyFromString(key string) (*ExtendedKey, error) {
+	// The base58-decoded extended key must consist of a serialized payload
+	// plus an additional 4 bytes for the checksum.
+	decoded := base58.Decode(key)
+	if len(decoded) != serializedKeyLen+4 {
+		return nil, ErrInvalidKeyLen
+	}
+
+	// The serialized format is:
+	//   version (4) || depth (1) || parent fingerprint (4)) ||
+	//   child num (4) || chain code (32) || key data (33) || checksum (4)
+
+	// Split the payload and checksum up and ensure the checksum matches.
+	payload := decoded[:len(decoded)-4]
+	checkSum := decoded[len(decoded)-4:]
+	expectedCheckSum := chainhash.DoubleHashB(payload)[:4]
+	if !bytes.Equal(checkSum, expectedCheckSum) {
+		return nil, ErrBadChecksum
+	}
+
+	// Deserialize each of the payload fields.
+	depth := payload[4:5][0]
+	parentFP := payload[5:9]
+	childNum := binary.BigEndian.Uint32(payload[9:13])
+	chainCode := payload[13:45]
+	keyData := payload[45:78]
+
+	// The key data is a private key if it starts with 0x00.  Serialized
+	// compressed pubkeys either start with 0x02 or 0x03.
+	isPrivate := keyData[0] == 0x00
+	if isPrivate {
+		// Ensure the private key is valid.  It must be within the range
+		// of the order of the secp256k1 curve and not be 0.
+		keyData = keyData[1:]
+		keyNum := new(big.Int).SetBytes(keyData)
+		if keyNum.Cmp(btcec.S256().N) >= 0 || keyNum.Sign() == 0 {
+			return nil, ErrUnusableSeed
+		}
+	} else {
+		// Ensure the public key parses correctly and is actually on the
+		// secp256k1 curve.
+		_, err := btcec.ParsePubKey(keyData, btcec.S256())
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return NewExtendedKey(keyData, chainCode, parentFP, depth,
+		childNum, isPrivate), nil
+}

+ 120 - 0
hd/hdwal.go

@@ -0,0 +1,120 @@
+package hd
+
+import (
+	"crypto/rand"
+	"crypto/sha256"
+	"encoding/binary"
+	"fmt"
+	"io"
+	"math"
+	"strconv"
+	"time"
+
+	"github.com/shirou/gopsutil/cpu"
+	"github.com/shirou/gopsutil/mem"
+	"github.com/shirou/gopsutil/net"
+	"github.com/tyler-smith/go-bip39"
+)
+
+type MnemonicType int
+
+const (
+	Mnemonic12 MnemonicType = iota
+	Mnemonic24
+)
+
+func (m MnemonicType) String() string {
+	switch m {
+	case Mnemonic12:
+		return "12 mnemonics"
+	case Mnemonic24:
+		return "24 mnemonics"
+	}
+
+	panic("Unexpected MnemonicType")
+}
+
+func entropy(mt MnemonicType) ([]byte, error) {
+	randomBytes := make([]byte, 0)
+	cpuPercent, _ := cpu.Percent(time.Second, false)
+	memory, _ := mem.VirtualMemory()
+
+	ioCounters, _ := net.IOCounters(true)
+	netWork := strconv.Itoa(int(ioCounters[0].BytesSent + ioCounters[0].BytesRecv))
+
+	cRandBytes := make([]byte, 32)
+	if _, err := io.ReadFull(rand.Reader, cRandBytes); err != nil {
+		return []byte{}, err
+	}
+
+	randomBytes = append(randomBytes, cRandBytes...)
+	randomBytes = append(randomBytes, float64ToByte(cpuPercent[0])...)
+	randomBytes = append(randomBytes, float64ToByte(memory.UsedPercent)...)
+	randomBytes = append(randomBytes, []byte(netWork)...)
+
+	random := sha256.Sum256(randomBytes)
+
+	switch mt {
+	case Mnemonic12:
+		return random[:16], nil
+	case Mnemonic24:
+		return random[:32], nil
+	default:
+		return nil, fmt.Errorf("MnemonicType err: %d", mt)
+	}
+}
+
+func NewMnemonic(mt MnemonicType) (string, error) {
+	entropyBytes, err := entropy(mt)
+	if err != nil {
+		return "", err
+	}
+
+	mnemonic, err := bip39.NewMnemonic(entropyBytes)
+	if err != nil {
+		return "", err
+	}
+	return mnemonic, nil
+}
+
+func CheckMnemonic(mnemonic string) bool {
+	return bip39.IsMnemonicValid(mnemonic)
+}
+
+func GenerateSeedFromMnemonic(mnemonic, password string) ([]byte, error) {
+	seedBytes, err := bip39.NewSeedWithErrorChecking(mnemonic, password)
+	if err != nil {
+		return []byte{}, err
+	}
+
+	return seedBytes, nil
+}
+
+func GetExtendSeedFromPath(path string, seed []byte) ([]byte, error) {
+	extendedKey, err := NewMaster(seed)
+	if err != nil {
+		return nil, err
+	}
+
+	derivationPath, err := ParseDerivationPath(path)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, index := range derivationPath {
+		childExtendedKey, err := extendedKey.Child(index)
+		if err != nil {
+			return nil, err
+		}
+		extendedKey = childExtendedKey
+	}
+
+	return extendedKey.key, nil
+}
+
+func float64ToByte(float float64) []byte {
+	bits := math.Float64bits(float)
+	bytes := make([]byte, 8)
+	binary.LittleEndian.PutUint64(bytes, bits)
+	return bytes
+}

+ 144 - 0
hd/parsePath.go

@@ -0,0 +1,144 @@
+package hd
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"math"
+	"math/big"
+	"strings"
+)
+
+// DefaultRootDerivationPath is the root path to which custom derivation endpoints
+// are appended. As such, the first account will be at m/44'/60'/0'/0, the second
+// at m/44'/60'/0'/1, etc.
+var DefaultRootDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}
+
+// DefaultBaseDerivationPath is the base path from which custom derivation endpoints
+// are incremented. As such, the first account will be at m/44'/60'/0'/0/0, the second
+// at m/44'/60'/0'/0/1, etc.
+var DefaultBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}
+
+// LegacyLedgerBaseDerivationPath is the legacy base path from which custom derivation
+// endpoints are incremented. As such, the first account will be at m/44'/60'/0'/0, the
+// second at m/44'/60'/0'/1, etc.
+var LegacyLedgerBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}
+
+// DerivationPath represents the computer friendly version of a hierarchical
+// deterministic wallet account derivaion path.
+//
+// The BIP-32 spec https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
+// defines derivation paths to be of the form:
+//
+//	m / purpose' / coin_type' / account' / change / address_index
+//
+// The BIP-44 spec https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
+// defines that the `purpose` be 44' (or 0x8000002C) for crypto currencies, and
+// SLIP-44 https://github.com/satoshilabs/slips/blob/master/slip-0044.md assigns
+// the `coin_type` 60' (or 0x8000003C) to Ethereum.
+//
+// The root path for Ethereum is m/44'/60'/0'/0 according to the specification
+// from https://github.com/ethereum/EIPs/issues/84, albeit it's not set in stone
+// yet whether accounts should increment the last component or the children of
+// that. We will go with the simpler approach of incrementing the last component.
+type DerivationPath []uint32
+
+// ParseDerivationPath converts a user specified derivation path string to the
+// internal binary representation.
+//
+// Full derivation paths need to start with the `m/` prefix, relative derivation
+// paths (which will get appended to the default root path) must not have prefixes
+// in front of the first element. Whitespace is ignored.
+func ParseDerivationPath(path string) (DerivationPath, error) {
+	var result DerivationPath
+
+	// Handle absolute or relative paths
+	components := strings.Split(path, "/")
+	switch {
+	case len(components) == 0:
+		return nil, errors.New("empty derivation path")
+
+	case strings.TrimSpace(components[0]) == "":
+		return nil, errors.New("ambiguous path: use 'm/' prefix for absolute paths, or no leading '/' for relative ones")
+
+	case strings.TrimSpace(components[0]) == "m":
+		components = components[1:]
+
+	default:
+		result = append(result, DefaultRootDerivationPath...)
+	}
+	// All remaining components are relative, append one by one
+	if len(components) == 0 {
+		return nil, errors.New("empty derivation path") // Empty relative paths
+	}
+	for _, component := range components {
+		// Ignore any user added whitespace
+		component = strings.TrimSpace(component)
+		var value uint32
+
+		// Handle hardened paths
+		if strings.HasSuffix(component, "'") {
+			value = 0x80000000
+			component = strings.TrimSpace(strings.TrimSuffix(component, "'"))
+		}
+		// Handle the non hardened component
+		bigval, ok := new(big.Int).SetString(component, 0)
+		if !ok {
+			return nil, fmt.Errorf("invalid component: %s", component)
+		}
+		max := math.MaxUint32 - value
+		if bigval.Sign() < 0 || bigval.Cmp(big.NewInt(int64(max))) > 0 {
+			if value == 0 {
+				return nil, fmt.Errorf("component %v out of allowed range [0, %d]", bigval, max)
+			}
+			return nil, fmt.Errorf("component %v out of allowed hardened range [0, %d]", bigval, max)
+		}
+		value += uint32(bigval.Uint64())
+
+		// Append and repeat
+		result = append(result, value)
+	}
+	return result, nil
+}
+
+// String implements the stringer interface, converting a binary derivation path
+// to its canonical representation.
+func (path DerivationPath) String() string {
+	result := "m"
+	for _, component := range path {
+		var hardened bool
+		if component >= 0x80000000 {
+			component -= 0x80000000
+			hardened = true
+		}
+		result = fmt.Sprintf("%s/%d", result, component)
+		if hardened {
+			result += "'"
+		}
+	}
+	return result
+}
+
+// MarshalJSON turns a derivation path into its json-serialized string
+func (path DerivationPath) MarshalJSON() ([]byte, error) {
+	return json.Marshal(path.String())
+}
+
+// UnmarshalJSON a json-serialized string back into a derivation path
+func (path *DerivationPath) UnmarshalJSON(b []byte) error {
+	var dp string
+	var err error
+	if err = json.Unmarshal(b, &dp); err != nil {
+		return err
+	}
+	*path, err = ParseDerivationPath(dp)
+	return err
+}
+
+func EthPath(index uint64) string {
+	return fmt.Sprintf("m/44'/60'/0'/0/%d", index)
+}
+
+func TronPath(index uint64) string {
+	return fmt.Sprintf("m/44'/195'/0'/0/%d", index)
+}

BIN
key-manager


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 193 - 0
key-manager.md


+ 97 - 0
key/key.go

@@ -0,0 +1,97 @@
+package key
+
+import (
+	"crypto/ecdsa"
+	"github.com/btcsuite/btcd/btcec"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
+	tronAddr "github.com/fbsobreira/gotron-sdk/pkg/address"
+	"key-manager/hd"
+)
+
+type Account interface {
+	GetIndex() uint64
+	GetPriKey() *ecdsa.PrivateKey
+	GetAddr() string
+}
+
+type TronAccount struct {
+	Index   uint64
+	PriKey  *ecdsa.PrivateKey
+	Address tronAddr.Address
+}
+
+func (t *TronAccount) GetIndex() uint64 {
+	return t.Index
+}
+
+func (t *TronAccount) GetPriKey() *ecdsa.PrivateKey {
+	return t.PriKey
+}
+
+func (t *TronAccount) GetAddr() string {
+	return t.Address.String()
+}
+
+type EthAccount struct {
+	Index   uint64
+	PriKey  *ecdsa.PrivateKey
+	Address common.Address
+}
+
+func (e *EthAccount) GetIndex() uint64 {
+	return e.Index
+}
+
+func (e *EthAccount) GetPriKey() *ecdsa.PrivateKey {
+	return e.PriKey
+}
+
+func (e *EthAccount) GetAddr() string {
+	return e.Address.String()
+}
+
+func NewEthAccount(mnemonic string, index uint64) (*EthAccount, error) {
+	seed, err := hd.GenerateSeedFromMnemonic(mnemonic, "")
+	if err != nil {
+		return nil, err
+	}
+
+	path := hd.EthPath(index)
+	extendSeed, err := hd.GetExtendSeedFromPath(path, seed)
+	if err != nil {
+		return nil, err
+	}
+
+	privateKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), extendSeed)
+	privateKeyECDSA := privateKey.ToECDSA()
+
+	return &EthAccount{
+		Index:   index,
+		PriKey:  privateKeyECDSA,
+		Address: crypto.PubkeyToAddress(privateKeyECDSA.PublicKey),
+	}, nil
+}
+
+func NewTronAccount(mnemonic string, index uint64) (*TronAccount, error) {
+	seed, err := hd.GenerateSeedFromMnemonic(mnemonic, "")
+	if err != nil {
+		return nil, err
+	}
+
+	path := hd.TronPath(index)
+	extendSeed, err := hd.GetExtendSeedFromPath(path, seed)
+	if err != nil {
+		return nil, err
+	}
+
+	privateKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), extendSeed)
+	privateKeyECDSA := privateKey.ToECDSA()
+
+	return &TronAccount{
+		Index:   index,
+		PriKey:  privateKeyECDSA,
+		Address: tronAddr.PubkeyToAddress(privateKeyECDSA.PublicKey),
+	}, nil
+
+}

+ 26 - 0
key/key_test.go

@@ -0,0 +1,26 @@
+package key
+
+import (
+	"key-manager/hd"
+	"testing"
+)
+
+func TestNewAccount(t *testing.T) {
+	mnemonic, err := GenerateMnemonic(hd.Mnemonic12)
+	if err != nil {
+		t.Fatal(err)
+	}
+	t.Log(mnemonic)
+
+	account, err := NewEthAccount(mnemonic, 0)
+	if err != nil {
+		t.Fatal(err)
+	}
+	t.Log(account.Address.String())
+
+	tronAccount, err := NewTronAccount(mnemonic, 0)
+	if err != nil {
+		t.Fatal(err)
+	}
+	t.Log(tronAccount.Address.String())
+}

+ 12 - 0
key/mnemonic.go

@@ -0,0 +1,12 @@
+package key
+
+import "key-manager/hd"
+
+func GenerateMnemonic(mType hd.MnemonicType) (string, error) {
+	mnemonic, err := hd.NewMnemonic(mType)
+	if err != nil {
+		return "", err
+	}
+
+	return mnemonic, nil
+}

+ 327 - 0
main.go

@@ -0,0 +1,327 @@
+package main
+
+import (
+	"encoding/hex"
+	"errors"
+	"fmt"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/console/prompt"
+	tronaddr "github.com/fbsobreira/gotron-sdk/pkg/address"
+	logging "github.com/ipfs/go-log/v2"
+	"github.com/urfave/cli/v2"
+	"google.golang.org/grpc"
+	"gorm.io/gorm"
+	"io/ioutil"
+	"key-manager/conf"
+	"key-manager/crypto"
+	"key-manager/dao"
+	"key-manager/service"
+	"net"
+	"os"
+	"strings"
+)
+
+var log = logging.Logger("main")
+
+func main() {
+	_ = logging.SetLogLevel("*", "INFO")
+
+	app := cli.App{
+		Name:  "key-manager",
+		Usage: "eth and tron key manager service",
+		Commands: []*cli.Command{
+			initCmd,
+			runCmd,
+			whitelistCmd,
+		},
+		EnableBashCompletion: true,
+	}
+
+	if err := app.Run(os.Args); err != nil {
+		fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", err)
+		os.Exit(1)
+	}
+}
+
+var initCmd = &cli.Command{
+	Name:  "init",
+	Usage: "init key-manager service",
+	Flags: []cli.Flag{
+		&cli.StringFlag{
+			Name:  "config",
+			Usage: "config file path",
+		},
+	},
+	Action: func(cctx *cli.Context) error {
+		err := conf.InitConfig(cctx.String("config"))
+		if err != nil {
+			return err
+		}
+		db, err := dao.InitMysqlDB()
+		if err != nil {
+			return err
+		}
+
+		_, err = db.GetPassword()
+		if err == nil {
+			return errors.New("password already set")
+		}
+
+		if !errors.Is(err, gorm.ErrRecordNotFound) {
+			return err
+		}
+
+		log.Info("Please set a password for the encrypted mnemonic")
+
+		password, err := stdinPassword(true)
+		if err != nil {
+			return err
+		}
+		scrypt := crypto.Scrypt(password)
+		scryptKey := hex.EncodeToString(scrypt)
+		err = db.CreatePassword(&dao.Password{
+			Password: scryptKey,
+		})
+
+		if err != nil {
+			return err
+		}
+
+		return nil
+	},
+}
+
+var runCmd = &cli.Command{
+	Name:  "run",
+	Usage: "run key-manager process",
+	Flags: []cli.Flag{
+		&cli.StringFlag{
+			Name:  "listen",
+			Usage: "The host address and port on which the key manager will listen",
+			Value: "127.0.0.1:5556",
+		},
+		&cli.StringFlag{
+			Name:  "config",
+			Usage: "config file path",
+		},
+		&cli.StringFlag{
+			Name:  "password",
+			Usage: "password file path",
+		},
+	},
+	Action: func(cctx *cli.Context) error {
+		err := conf.InitConfig(cctx.String("config"))
+		if err != nil {
+			return err
+		}
+		db, err := dao.InitMysqlDB()
+		if err != nil {
+			return err
+		}
+
+		scrypt, err := db.GetPassword()
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return errors.New("please set a password first. run: key-manager init")
+		} else if err != nil {
+			return err
+		}
+
+		scryptKey, err := hex.DecodeString(scrypt)
+		if err != nil {
+			return err
+		}
+
+		var password string
+
+		if cctx.IsSet("password") {
+			p := cctx.String("password")
+			data, err := ioutil.ReadFile(p)
+			if err != nil {
+				return err
+			}
+			password = strings.TrimSpace(string(data))
+		} else {
+			var err error
+			password, err = stdinPassword(false)
+			if err != nil {
+				return err
+			}
+		}
+
+		ok, err := crypto.VerifyScrypt(password, scryptKey)
+		if err != nil {
+			return err
+		}
+		if !ok {
+			return errors.New("password verification failed")
+		}
+
+		listen := cctx.String("listen")
+		listener, err := net.Listen("tcp", listen)
+		if err != nil {
+			return err
+		}
+
+		log.Infof("grpc server Listing on: %s", listen)
+
+		grpcServer := grpc.NewServer()
+		server, err := service.NewKeyManager(db, password)
+		if err != nil {
+			return err
+		}
+
+		service.RegisterKeyManagerServer(grpcServer, server)
+		if err = grpcServer.Serve(listener); err != nil {
+			log.Error(err)
+			return err
+		}
+
+		return nil
+	},
+}
+
+var whitelistCmd = &cli.Command{
+	Name:  "whitelist",
+	Usage: "whitelist tools",
+	Subcommands: []*cli.Command{
+		listCmd,
+		addCmd,
+		deleteCmd,
+	},
+}
+
+var addCmd = &cli.Command{
+	Name:      "add",
+	Usage:     "add whitelist address",
+	ArgsUsage: "[name] [address]",
+	Flags: []cli.Flag{
+		&cli.StringFlag{
+			Name:  "config",
+			Usage: "config file path",
+		},
+	},
+	Action: func(cctx *cli.Context) error {
+		err := conf.InitConfig(cctx.String("config"))
+		if err != nil {
+			return err
+		}
+		db, err := dao.InitMysqlDB()
+		if err != nil {
+			return err
+		}
+		if cctx.NArg() != 2 {
+			return errors.New("params need [name] [address]")
+		}
+
+		name := cctx.Args().Get(0)
+		address := cctx.Args().Get(1)
+		if address[:2] == "0x" {
+			address = common.HexToAddress(address).String()
+		} else {
+			addr, err := tronaddr.Base58ToAddress(address)
+			if err != nil {
+				return err
+			}
+			address = addr.String()
+		}
+
+		err = db.CreateWhitelist(&dao.Whitelist{
+			Name:         name,
+			WhiteAddress: address,
+		})
+		if err != nil {
+			return err
+		}
+
+		fmt.Printf("add whitelist address success, name: %s, addr: %s \n", name, address)
+
+		return nil
+	},
+}
+
+var deleteCmd = &cli.Command{
+	Name:      "delete",
+	Usage:     "delete whitelist address",
+	ArgsUsage: "[name]",
+	Flags: []cli.Flag{
+		&cli.StringFlag{
+			Name:  "config",
+			Usage: "config file path",
+		},
+	},
+	Action: func(cctx *cli.Context) error {
+		err := conf.InitConfig(cctx.String("config"))
+		if err != nil {
+			return err
+		}
+		db, err := dao.InitMysqlDB()
+		if err != nil {
+			return err
+		}
+		if cctx.NArg() != 1 {
+			return errors.New("params need [name]")
+		}
+
+		name := cctx.Args().Get(0)
+
+		err = db.DeleteWhitelist(name)
+		if err != nil {
+			return err
+		}
+
+		fmt.Printf("delete whitelist address success, name: %s \n", name)
+
+		return nil
+	},
+}
+
+var listCmd = &cli.Command{
+	Name:  "list",
+	Usage: "list whitelist address",
+	Flags: []cli.Flag{
+		&cli.StringFlag{
+			Name:  "config",
+			Usage: "config file path",
+		},
+	},
+	Action: func(cctx *cli.Context) error {
+		err := conf.InitConfig(cctx.String("config"))
+		if err != nil {
+			return err
+		}
+		db, err := dao.InitMysqlDB()
+		if err != nil {
+			return err
+		}
+
+		list, err := db.GetAllWhitelists()
+		if err != nil {
+			return err
+		}
+
+		for _, l := range list {
+			fmt.Printf("whitelist address, name: %s, addr: %s \n", l.Name, l.WhiteAddress)
+		}
+
+		return nil
+	},
+}
+
+func stdinPassword(isConfirm bool) (string, error) {
+	password, err := prompt.Stdin.PromptPassword("Password: ")
+	if err != nil {
+		return "", err
+	}
+
+	if isConfirm {
+		confirm, err := prompt.Stdin.PromptPassword("Confirm Password: ")
+		if err != nil {
+			return "", fmt.Errorf("failed to read password confirmation: %v", err)
+		}
+		if password != confirm {
+			return "", errors.New("passwords do not match")
+		}
+	}
+
+	return password, nil
+}

+ 1 - 0
pass

@@ -0,0 +1 @@
+Wangliang918@

+ 65 - 0
proto/keyManager.proto

@@ -0,0 +1,65 @@
+syntax = "proto3";
+package service;
+
+option java_package = "proto.keyManager";
+option java_outer_classname = "keyManagerProto";
+
+option go_package = "../service";
+
+message CreateMnemonicRequest {
+    string name = 1; // 商户名字,名字全局唯一,每个商户只能生成一个助记词
+    int64 mnemonicType = 2; // 0 or 1, 0代表12个助记词,1代表24个助记词
+}
+
+message CreateMnemonicResponse {
+  string code = 1;  // 响应码, 成功是200
+  string msg = 2;   // 响应描述信息
+}
+
+message CreateKeyRequest {
+    string name = 1; // 商户名字,如果该商户未创建助记词,则返回错误
+    string network = 2; // 链网络名称,"eth" 或 "tron"
+    int64 index = 3; // 派生账户使用的index,如果该index已被使用,则返回错误
+}
+
+message GetIndexRequest {
+    string network = 1; // 链网络名称,"eth" 或 "tron"
+    string address = 2; // 查询该地址对应的商户名字和index
+}
+
+message GetAddressRequest {
+    string name = 1; // 商户名字,如果该商户未创建助记词,则返回错误
+    string network = 2; // 链网络名称,"eth" 或 "tron"
+    int64 index = 3; // 派生账户使用的index
+}
+
+message KeyResponse {
+  string code = 1;  // 响应码, 成功是200
+  string msg = 2;   // 响应描述信息
+  string network = 3; // 链网络名称,"eth" 或 "tron"
+  string name = 4; // 商户名字
+  int64 index = 5; // 派生账户使用的index
+  string address = 6; // 地址
+}
+
+message SignRequest {
+   string network = 1; // 链网络名称,"eth" 或 "tron"
+   string sender = 2; // 签名地址
+   string tx = 3; // 编码的交易,先将eth/tron交易Marshal为字符数组,再编码为hex字符串
+}
+
+message SignResponse {
+  string code = 1;  // 响应码, 成功是200
+  string msg = 2;   // 响应描述信息
+  string network = 3; // 链网络名称,"eth" 或 "tron"
+  string sender = 4; // 签名地址
+  string signedTx = 5; // 编码的已签名交易,先将eth/tron已签名交易Marshal为字符数组,再编码为hex字符串
+}
+
+service KeyManager {
+    rpc CreateMnemonic(CreateMnemonicRequest) returns(CreateMnemonicResponse) {}
+    rpc CreateKey(CreateKeyRequest) returns(KeyResponse) {}
+    rpc GetIndex(GetIndexRequest) returns(KeyResponse) {}
+    rpc GetAddress(GetAddressRequest) returns(KeyResponse) {}
+    rpc Sign(SignRequest) returns(SignResponse) {}
+}

+ 103 - 0
readme.md

@@ -0,0 +1,103 @@
+# key-manager
+
+## 编译
+
+make build
+
+## 使用
+
+```
+./key-manager -h
+NAME:
+   key-manager - eth and tron key manager service
+
+USAGE:
+   key-manager [global options] command [command options] [arguments...]
+
+COMMANDS:
+   init       init key-manager service
+   run        run key-manager process
+   whitelist  whitelist tools
+   help, h    Shows a list of commands or help for one command
+
+GLOBAL OPTIONS:
+   --help, -h  show help (default: false)
+```
+
+### 步骤
+
+#### 1.配置
+
+服务依赖于mysql,需在conf.yaml中完成mysql的配置
+
+```
+mysql:
+  user: root
+  password: 123456789
+  host: 127.0.0.1:3306
+  db: key-manager
+```
+
+#### 2.初始化
+
+使用前需要设置密码,用来加密助记词使用,并且在启动前将检查输入的密码是否正确。在真实环境启动时,采取的是后台启动,所以可以先将密码写在一个文件中,将这个文件的路径传递给key-manager,当key-manager启动后,**删除密码文件来保证安全**。
+
+命令示例:
+
+`./key-manager init --config ./conf/conf.yaml`
+
+#### 3.白名单
+
+可通过mysql插入白名单地址,key-manager也提供了命令方便查询和查询以及删除白名单地址
+
+```
+ ./key-manager whitelist -h
+NAME:
+   key-manager whitelist - whitelist tools
+
+USAGE:
+   key-manager whitelist command [command options] [arguments...]
+
+COMMANDS:
+   list     list whitelist address
+   add      add whitelist address
+   delete   delete whitelist address
+   help, h  Shows a list of commands or help for one command
+
+OPTIONS:
+   --help, -h  show help (default: false)
+
+```
+
+命令示例:
+
+```
+./key-manager whitelist --config ./conf/conf.yaml list
+
+./key-manager whitelist add --config ./conf/conf.yaml t5 0x138d5D3C2d7d68bFC653726c8a5E8bA301452202
+
+./key-manager whitelist delete --config ./conf/conf.yaml t2
+```
+
+#### 4.启动
+
+```
+./key-manager run -h
+NAME:
+   key-manager run - run key-manager process
+
+USAGE:
+   key-manager run [command options] [arguments...]
+
+OPTIONS:
+   --config value    config file path
+   --listen value    The host address and port on which the key manager will listen (default: "127.0.0.1:5556")
+   --password value  password file path
+```
+
+- --listen 可指定服务启动的端口
+- --password 当后台启动时,可指定服务的密码,启动后,一定删除密码文件
+
+命令示例:
+
+`./key-manager run --config ./conf/conf.yaml`

+ 50 - 0
service/const.go

@@ -0,0 +1,50 @@
+package service
+
+const (
+	eth  = "eth"
+	tron = "tron"
+)
+
+const (
+	okCode = "200"
+	okMsg  = "success"
+
+	dbErrCode = "1000"
+
+	mnemonicTypeErrCode = "1001"
+	mnemonicTypeErrMsg  = "MnemonicType must be: 0 or 1"
+
+	mnemonicNameErrCode = "1002"
+	mnemonicNameErrMsg  = "Mnemonic name cannot be empty"
+
+	GenerateMnemonicErrCode = "1003"
+	encryptMnemonicErrCode  = "1004"
+
+	mnemonicNotExistErrCode = "1005"
+	mnemonicNotExistErrMsg  = "User mnemonic does not exist"
+
+	createKeyErrCode = "1006"
+
+	addressErrCode = "1007"
+	addressErrMsg  = "Address format failed"
+
+	networkErrCode = "1008"
+	networkErrMsg  = "Network must be eth or tron"
+
+	loadSignerErrCode       = "1009"
+	unmarshalJEthTxErrCode  = "1010"
+	checkErcTxErrCode       = "1011"
+	signEthTxErrCode        = "1012"
+	marshalJEthTxErrCode    = "1013"
+	unmarshalJTronTxErrCode = "1014"
+	checkTronTxErrCode      = "1015"
+	signTronTxErrCode       = "1016"
+	marshalJTronTxErrCode   = "1017"
+)
+
+const (
+	ethCoin  = "eth"
+	trxCoin  = "trx"
+	usdtCoin = "usdt"
+	usdcCoin = "usdc"
+)

+ 171 - 0
service/key.go

@@ -0,0 +1,171 @@
+package service
+
+import (
+	"context"
+	"errors"
+	"key-manager/dao"
+	"key-manager/key"
+)
+
+func (km *KeyManager) CreateKey(ctx context.Context, req *CreateKeyRequest) (*KeyResponse, error) {
+	km.lk.Lock()
+	defer km.lk.Unlock()
+
+	if _, ok := km.mnemonics[req.Name]; !ok {
+		return &KeyResponse{
+			Code:    mnemonicNotExistErrCode,
+			Msg:     mnemonicNotExistErrMsg,
+			Network: req.Network,
+			Name:    req.Name,
+			Index:   req.Index,
+		}, nil
+	}
+
+	account, err := createKey(km.mnemonics[req.Name], req.Network, req.Index)
+	if err != nil {
+		return &KeyResponse{
+			Code:    createKeyErrCode,
+			Msg:     err.Error(),
+			Network: req.Network,
+			Name:    req.Name,
+			Index:   req.Index,
+		}, nil
+	}
+
+	if req.Network == eth {
+		err = km.dao.CreateEth(&dao.EthAddressInfo{
+			Name:       req.Name,
+			EthIndex:   req.Index,
+			EthAddress: account.GetAddr(),
+		})
+	} else {
+		err = km.dao.CreateTron(&dao.TronAddressInfo{
+			Name:        req.Name,
+			TronIndex:   req.Index,
+			TronAddress: account.GetAddr(),
+		})
+	}
+
+	if err != nil {
+		return &KeyResponse{
+			Code:    dbErrCode,
+			Msg:     err.Error(),
+			Network: req.Network,
+			Name:    req.Name,
+			Index:   req.Index,
+		}, nil
+	}
+
+	km.signers[account.GetAddr()] = account.GetPriKey()
+
+	return &KeyResponse{
+		Code:    okCode,
+		Msg:     okMsg,
+		Network: req.Network,
+		Name:    req.Name,
+		Index:   req.Index,
+		Address: account.GetAddr(),
+	}, nil
+}
+
+func createKey(mnemonic string, network string, index int64) (key.Account, error) {
+	switch network {
+	case eth:
+		return key.NewEthAccount(mnemonic, uint64(index))
+	case tron:
+		return key.NewTronAccount(mnemonic, uint64(index))
+	default:
+		return nil, errors.New("network must be eth or tron")
+	}
+}
+
+func (km *KeyManager) GetIndex(ctx context.Context, req *GetIndexRequest) (*KeyResponse, error) {
+	km.lk.Lock()
+	defer km.lk.Unlock()
+	addr, err := format(req.Network, req.Address)
+	if err != nil {
+		return &KeyResponse{
+			Code:    addressErrCode,
+			Msg:     addressErrMsg,
+			Network: req.Network,
+			Name:    "",
+			Index:   -1,
+		}, nil
+	}
+
+	var info dao.AccountInfo
+	switch req.Network {
+	case eth:
+		info, err = km.dao.GetEthFromAddress(addr)
+	case tron:
+		info, err = km.dao.GetTronFromAddress(addr)
+	default:
+		return &KeyResponse{
+			Code:    networkErrCode,
+			Msg:     networkErrMsg,
+			Network: req.Network,
+			Name:    "",
+			Index:   -1,
+		}, nil
+	}
+
+	if err != nil {
+		return &KeyResponse{
+			Code:    dbErrCode,
+			Msg:     err.Error(),
+			Network: req.Network,
+			Name:    "",
+			Index:   -1,
+		}, nil
+	}
+
+	return &KeyResponse{
+		Code:    okCode,
+		Msg:     okMsg,
+		Network: req.Network,
+		Name:    info.GetName(),
+		Index:   info.GetIndex(),
+		Address: info.GetAddr(),
+	}, nil
+}
+
+func (km *KeyManager) GetAddress(ctx context.Context, req *GetAddressRequest) (*KeyResponse, error) {
+	km.lk.Lock()
+	defer km.lk.Unlock()
+
+	var info dao.AccountInfo
+	var err error
+	switch req.Network {
+	case eth:
+		info, err = km.dao.GetEthFromIndex(req.Name, req.Index)
+	case tron:
+		info, err = km.dao.GetTronFromIndex(req.Name, req.Index)
+	default:
+		return &KeyResponse{
+			Code:    networkErrCode,
+			Msg:     networkErrMsg,
+			Network: req.Network,
+			Name:    "",
+			Index:   -1,
+		}, nil
+	}
+
+	if err != nil {
+		return &KeyResponse{
+			Code:    dbErrCode,
+			Msg:     err.Error(),
+			Network: req.Network,
+			Name:    "",
+			Index:   -1,
+		}, nil
+	}
+
+	return &KeyResponse{
+		Code:    okCode,
+		Msg:     okMsg,
+		Network: req.Network,
+		Name:    info.GetName(),
+		Index:   info.GetIndex(),
+		Address: info.GetAddr(),
+	}, nil
+}

+ 785 - 0
service/keyManager.pb.go

@@ -0,0 +1,785 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.27.1
+// 	protoc        v3.19.3
+// source: keyManager.proto
+
+package service
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type CreateMnemonicRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Name         string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`                  // 商户名字,名字全局唯一,每个商户只能生成一个助记词
+	MnemonicType int64  `protobuf:"varint,2,opt,name=mnemonicType,proto3" json:"mnemonicType,omitempty"` // 0 or 1, 0代表12个助记词,1代表24个助记词
+}
+
+func (x *CreateMnemonicRequest) Reset() {
+	*x = CreateMnemonicRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_keyManager_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *CreateMnemonicRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CreateMnemonicRequest) ProtoMessage() {}
+
+func (x *CreateMnemonicRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_keyManager_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use CreateMnemonicRequest.ProtoReflect.Descriptor instead.
+func (*CreateMnemonicRequest) Descriptor() ([]byte, []int) {
+	return file_keyManager_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *CreateMnemonicRequest) GetName() string {
+	if x != nil {
+		return x.Name
+	}
+	return ""
+}
+
+func (x *CreateMnemonicRequest) GetMnemonicType() int64 {
+	if x != nil {
+		return x.MnemonicType
+	}
+	return 0
+}
+
+type CreateMnemonicResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Code string `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"` // 响应码, 成功是200
+	Msg  string `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"`   // 响应描述信息
+}
+
+func (x *CreateMnemonicResponse) Reset() {
+	*x = CreateMnemonicResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_keyManager_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *CreateMnemonicResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CreateMnemonicResponse) ProtoMessage() {}
+
+func (x *CreateMnemonicResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_keyManager_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use CreateMnemonicResponse.ProtoReflect.Descriptor instead.
+func (*CreateMnemonicResponse) Descriptor() ([]byte, []int) {
+	return file_keyManager_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *CreateMnemonicResponse) GetCode() string {
+	if x != nil {
+		return x.Code
+	}
+	return ""
+}
+
+func (x *CreateMnemonicResponse) GetMsg() string {
+	if x != nil {
+		return x.Msg
+	}
+	return ""
+}
+
+type CreateKeyRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Name    string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`       // 商户名字,如果该商户未创建助记词,则返回错误
+	Network string `protobuf:"bytes,2,opt,name=network,proto3" json:"network,omitempty"` // 链网络名称,"eth" 或 "tron"
+	Index   int64  `protobuf:"varint,3,opt,name=index,proto3" json:"index,omitempty"`    // 派生账户使用的index,如果该index已被使用,则返回错误
+}
+
+func (x *CreateKeyRequest) Reset() {
+	*x = CreateKeyRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_keyManager_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *CreateKeyRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CreateKeyRequest) ProtoMessage() {}
+
+func (x *CreateKeyRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_keyManager_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use CreateKeyRequest.ProtoReflect.Descriptor instead.
+func (*CreateKeyRequest) Descriptor() ([]byte, []int) {
+	return file_keyManager_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *CreateKeyRequest) GetName() string {
+	if x != nil {
+		return x.Name
+	}
+	return ""
+}
+
+func (x *CreateKeyRequest) GetNetwork() string {
+	if x != nil {
+		return x.Network
+	}
+	return ""
+}
+
+func (x *CreateKeyRequest) GetIndex() int64 {
+	if x != nil {
+		return x.Index
+	}
+	return 0
+}
+
+type GetIndexRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Network string `protobuf:"bytes,1,opt,name=network,proto3" json:"network,omitempty"` // 链网络名称,"eth" 或 "tron"
+	Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` // 查询该地址对应的商户名字和index
+}
+
+func (x *GetIndexRequest) Reset() {
+	*x = GetIndexRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_keyManager_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *GetIndexRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetIndexRequest) ProtoMessage() {}
+
+func (x *GetIndexRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_keyManager_proto_msgTypes[3]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetIndexRequest.ProtoReflect.Descriptor instead.
+func (*GetIndexRequest) Descriptor() ([]byte, []int) {
+	return file_keyManager_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *GetIndexRequest) GetNetwork() string {
+	if x != nil {
+		return x.Network
+	}
+	return ""
+}
+
+func (x *GetIndexRequest) GetAddress() string {
+	if x != nil {
+		return x.Address
+	}
+	return ""
+}
+
+type GetAddressRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Name    string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`       // 商户名字,如果该商户未创建助记词,则返回错误
+	Network string `protobuf:"bytes,2,opt,name=network,proto3" json:"network,omitempty"` // 链网络名称,"eth" 或 "tron"
+	Index   int64  `protobuf:"varint,3,opt,name=index,proto3" json:"index,omitempty"`    // 派生账户使用的index
+}
+
+func (x *GetAddressRequest) Reset() {
+	*x = GetAddressRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_keyManager_proto_msgTypes[4]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *GetAddressRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetAddressRequest) ProtoMessage() {}
+
+func (x *GetAddressRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_keyManager_proto_msgTypes[4]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetAddressRequest.ProtoReflect.Descriptor instead.
+func (*GetAddressRequest) Descriptor() ([]byte, []int) {
+	return file_keyManager_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *GetAddressRequest) GetName() string {
+	if x != nil {
+		return x.Name
+	}
+	return ""
+}
+
+func (x *GetAddressRequest) GetNetwork() string {
+	if x != nil {
+		return x.Network
+	}
+	return ""
+}
+
+func (x *GetAddressRequest) GetIndex() int64 {
+	if x != nil {
+		return x.Index
+	}
+	return 0
+}
+
+type KeyResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Code    string `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"`       // 响应码, 成功是200
+	Msg     string `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"`         // 响应描述信息
+	Network string `protobuf:"bytes,3,opt,name=network,proto3" json:"network,omitempty"` // 链网络名称,"eth" 或 "tron"
+	Name    string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"`       // 商户名字
+	Index   int64  `protobuf:"varint,5,opt,name=index,proto3" json:"index,omitempty"`    // 派生账户使用的index
+	Address string `protobuf:"bytes,6,opt,name=address,proto3" json:"address,omitempty"` // 地址
+}
+
+func (x *KeyResponse) Reset() {
+	*x = KeyResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_keyManager_proto_msgTypes[5]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *KeyResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*KeyResponse) ProtoMessage() {}
+
+func (x *KeyResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_keyManager_proto_msgTypes[5]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use KeyResponse.ProtoReflect.Descriptor instead.
+func (*KeyResponse) Descriptor() ([]byte, []int) {
+	return file_keyManager_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *KeyResponse) GetCode() string {
+	if x != nil {
+		return x.Code
+	}
+	return ""
+}
+
+func (x *KeyResponse) GetMsg() string {
+	if x != nil {
+		return x.Msg
+	}
+	return ""
+}
+
+func (x *KeyResponse) GetNetwork() string {
+	if x != nil {
+		return x.Network
+	}
+	return ""
+}
+
+func (x *KeyResponse) GetName() string {
+	if x != nil {
+		return x.Name
+	}
+	return ""
+}
+
+func (x *KeyResponse) GetIndex() int64 {
+	if x != nil {
+		return x.Index
+	}
+	return 0
+}
+
+func (x *KeyResponse) GetAddress() string {
+	if x != nil {
+		return x.Address
+	}
+	return ""
+}
+
+type SignRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Network string `protobuf:"bytes,1,opt,name=network,proto3" json:"network,omitempty"` // 链网络名称,"eth" 或 "tron"
+	Sender  string `protobuf:"bytes,2,opt,name=sender,proto3" json:"sender,omitempty"`   // 签名地址
+	Tx      string `protobuf:"bytes,3,opt,name=tx,proto3" json:"tx,omitempty"`           // 编码的交易,先将eth/tron交易Marshal为字符数组,再编码为hex字符串
+}
+
+func (x *SignRequest) Reset() {
+	*x = SignRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_keyManager_proto_msgTypes[6]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SignRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SignRequest) ProtoMessage() {}
+
+func (x *SignRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_keyManager_proto_msgTypes[6]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use SignRequest.ProtoReflect.Descriptor instead.
+func (*SignRequest) Descriptor() ([]byte, []int) {
+	return file_keyManager_proto_rawDescGZIP(), []int{6}
+}
+
+func (x *SignRequest) GetNetwork() string {
+	if x != nil {
+		return x.Network
+	}
+	return ""
+}
+
+func (x *SignRequest) GetSender() string {
+	if x != nil {
+		return x.Sender
+	}
+	return ""
+}
+
+func (x *SignRequest) GetTx() string {
+	if x != nil {
+		return x.Tx
+	}
+	return ""
+}
+
+type SignResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Code     string `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"`         // 响应码, 成功是200
+	Msg      string `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"`           // 响应描述信息
+	Network  string `protobuf:"bytes,3,opt,name=network,proto3" json:"network,omitempty"`   // 链网络名称,"eth" 或 "tron"
+	Sender   string `protobuf:"bytes,4,opt,name=sender,proto3" json:"sender,omitempty"`     // 签名地址
+	SignedTx string `protobuf:"bytes,5,opt,name=signedTx,proto3" json:"signedTx,omitempty"` // 编码的已签名交易,先将eth/tron已签名交易Marshal为字符数组,再编码为hex字符串
+}
+
+func (x *SignResponse) Reset() {
+	*x = SignResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_keyManager_proto_msgTypes[7]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SignResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SignResponse) ProtoMessage() {}
+
+func (x *SignResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_keyManager_proto_msgTypes[7]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use SignResponse.ProtoReflect.Descriptor instead.
+func (*SignResponse) Descriptor() ([]byte, []int) {
+	return file_keyManager_proto_rawDescGZIP(), []int{7}
+}
+
+func (x *SignResponse) GetCode() string {
+	if x != nil {
+		return x.Code
+	}
+	return ""
+}
+
+func (x *SignResponse) GetMsg() string {
+	if x != nil {
+		return x.Msg
+	}
+	return ""
+}
+
+func (x *SignResponse) GetNetwork() string {
+	if x != nil {
+		return x.Network
+	}
+	return ""
+}
+
+func (x *SignResponse) GetSender() string {
+	if x != nil {
+		return x.Sender
+	}
+	return ""
+}
+
+func (x *SignResponse) GetSignedTx() string {
+	if x != nil {
+		return x.SignedTx
+	}
+	return ""
+}
+
+var File_keyManager_proto protoreflect.FileDescriptor
+
+var file_keyManager_proto_rawDesc = []byte{
+	0x0a, 0x10, 0x6b, 0x65, 0x79, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x12, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0x4f, 0x0a, 0x15, 0x43,
+	0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x6e, 0x65, 0x6d, 0x6f, 0x6e, 0x69, 0x63, 0x52, 0x65, 0x71,
+	0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x6d, 0x6e, 0x65, 0x6d,
+	0x6f, 0x6e, 0x69, 0x63, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c,
+	0x6d, 0x6e, 0x65, 0x6d, 0x6f, 0x6e, 0x69, 0x63, 0x54, 0x79, 0x70, 0x65, 0x22, 0x3e, 0x0a, 0x16,
+	0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x6e, 0x65, 0x6d, 0x6f, 0x6e, 0x69, 0x63, 0x52, 0x65,
+	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73,
+	0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x22, 0x56, 0x0a, 0x10,
+	0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
+	0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x14,
+	0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x69,
+	0x6e, 0x64, 0x65, 0x78, 0x22, 0x45, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78,
+	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f,
+	0x72, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72,
+	0x6b, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x57, 0x0a, 0x11, 0x47,
+	0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
+	0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x14,
+	0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x69,
+	0x6e, 0x64, 0x65, 0x78, 0x22, 0x91, 0x01, 0x0a, 0x0b, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70,
+	0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65,
+	0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74,
+	0x77, 0x6f, 0x72, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65,
+	0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x18,
+	0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x4f, 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e,
+	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f,
+	0x72, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72,
+	0x6b, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x78, 0x18,
+	0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x74, 0x78, 0x22, 0x82, 0x01, 0x0a, 0x0c, 0x53, 0x69,
+	0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f,
+	0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x10,
+	0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x73, 0x67,
+	0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65,
+	0x6e, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64,
+	0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x78, 0x18, 0x05,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x78, 0x32, 0xd8,
+	0x02, 0x0a, 0x0a, 0x4b, 0x65, 0x79, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x12, 0x53, 0x0a,
+	0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x6e, 0x65, 0x6d, 0x6f, 0x6e, 0x69, 0x63, 0x12,
+	0x1e, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
+	0x4d, 0x6e, 0x65, 0x6d, 0x6f, 0x6e, 0x69, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
+	0x1f, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
+	0x4d, 0x6e, 0x65, 0x6d, 0x6f, 0x6e, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
+	0x22, 0x00, 0x12, 0x3e, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12,
+	0x19, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
+	0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x73, 0x65, 0x72,
+	0x76, 0x69, 0x63, 0x65, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
+	0x22, 0x00, 0x12, 0x3c, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x18,
+	0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x64, 0x65,
+	0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69,
+	0x63, 0x65, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
+	0x12, 0x40, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1a,
+	0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72,
+	0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x73, 0x65, 0x72,
+	0x76, 0x69, 0x63, 0x65, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
+	0x22, 0x00, 0x12, 0x35, 0x0a, 0x04, 0x53, 0x69, 0x67, 0x6e, 0x12, 0x14, 0x2e, 0x73, 0x65, 0x72,
+	0x76, 0x69, 0x63, 0x65, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x1a, 0x15, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52,
+	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x2f, 0x0a, 0x10, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x2e, 0x6b, 0x65, 0x79, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x42, 0x0f, 0x6b,
+	0x65, 0x79, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x5a, 0x0a,
+	0x2e, 0x2e, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x33,
+}
+
+var (
+	file_keyManager_proto_rawDescOnce sync.Once
+	file_keyManager_proto_rawDescData = file_keyManager_proto_rawDesc
+)
+
+func file_keyManager_proto_rawDescGZIP() []byte {
+	file_keyManager_proto_rawDescOnce.Do(func() {
+		file_keyManager_proto_rawDescData = protoimpl.X.CompressGZIP(file_keyManager_proto_rawDescData)
+	})
+	return file_keyManager_proto_rawDescData
+}
+
+var file_keyManager_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
+var file_keyManager_proto_goTypes = []interface{}{
+	(*CreateMnemonicRequest)(nil),  // 0: service.CreateMnemonicRequest
+	(*CreateMnemonicResponse)(nil), // 1: service.CreateMnemonicResponse
+	(*CreateKeyRequest)(nil),       // 2: service.CreateKeyRequest
+	(*GetIndexRequest)(nil),        // 3: service.GetIndexRequest
+	(*GetAddressRequest)(nil),      // 4: service.GetAddressRequest
+	(*KeyResponse)(nil),            // 5: service.KeyResponse
+	(*SignRequest)(nil),            // 6: service.SignRequest
+	(*SignResponse)(nil),           // 7: service.SignResponse
+}
+var file_keyManager_proto_depIdxs = []int32{
+	0, // 0: service.KeyManager.CreateMnemonic:input_type -> service.CreateMnemonicRequest
+	2, // 1: service.KeyManager.CreateKey:input_type -> service.CreateKeyRequest
+	3, // 2: service.KeyManager.GetIndex:input_type -> service.GetIndexRequest
+	4, // 3: service.KeyManager.GetAddress:input_type -> service.GetAddressRequest
+	6, // 4: service.KeyManager.Sign:input_type -> service.SignRequest
+	1, // 5: service.KeyManager.CreateMnemonic:output_type -> service.CreateMnemonicResponse
+	5, // 6: service.KeyManager.CreateKey:output_type -> service.KeyResponse
+	5, // 7: service.KeyManager.GetIndex:output_type -> service.KeyResponse
+	5, // 8: service.KeyManager.GetAddress:output_type -> service.KeyResponse
+	7, // 9: service.KeyManager.Sign:output_type -> service.SignResponse
+	5, // [5:10] is the sub-list for method output_type
+	0, // [0:5] is the sub-list for method input_type
+	0, // [0:0] is the sub-list for extension type_name
+	0, // [0:0] is the sub-list for extension extendee
+	0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_keyManager_proto_init() }
+func file_keyManager_proto_init() {
+	if File_keyManager_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_keyManager_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*CreateMnemonicRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_keyManager_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*CreateMnemonicResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_keyManager_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*CreateKeyRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_keyManager_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*GetIndexRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_keyManager_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*GetAddressRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_keyManager_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*KeyResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_keyManager_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SignRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_keyManager_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SignResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_keyManager_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   8,
+			NumExtensions: 0,
+			NumServices:   1,
+		},
+		GoTypes:           file_keyManager_proto_goTypes,
+		DependencyIndexes: file_keyManager_proto_depIdxs,
+		MessageInfos:      file_keyManager_proto_msgTypes,
+	}.Build()
+	File_keyManager_proto = out.File
+	file_keyManager_proto_rawDesc = nil
+	file_keyManager_proto_goTypes = nil
+	file_keyManager_proto_depIdxs = nil
+}

+ 249 - 0
service/keyManager_grpc.pb.go

@@ -0,0 +1,249 @@
+// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+// versions:
+// - protoc-gen-go-grpc v1.2.0
+// - protoc             v3.19.3
+// source: keyManager.proto
+
+package service
+
+import (
+	context "context"
+	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+// Requires gRPC-Go v1.32.0 or later.
+const _ = grpc.SupportPackageIsVersion7
+
+// KeyManagerClient is the client API for KeyManager service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
+type KeyManagerClient interface {
+	CreateMnemonic(ctx context.Context, in *CreateMnemonicRequest, opts ...grpc.CallOption) (*CreateMnemonicResponse, error)
+	CreateKey(ctx context.Context, in *CreateKeyRequest, opts ...grpc.CallOption) (*KeyResponse, error)
+	GetIndex(ctx context.Context, in *GetIndexRequest, opts ...grpc.CallOption) (*KeyResponse, error)
+	GetAddress(ctx context.Context, in *GetAddressRequest, opts ...grpc.CallOption) (*KeyResponse, error)
+	Sign(ctx context.Context, in *SignRequest, opts ...grpc.CallOption) (*SignResponse, error)
+}
+
+type keyManagerClient struct {
+	cc grpc.ClientConnInterface
+}
+
+func NewKeyManagerClient(cc grpc.ClientConnInterface) KeyManagerClient {
+	return &keyManagerClient{cc}
+}
+
+func (c *keyManagerClient) CreateMnemonic(ctx context.Context, in *CreateMnemonicRequest, opts ...grpc.CallOption) (*CreateMnemonicResponse, error) {
+	out := new(CreateMnemonicResponse)
+	err := c.cc.Invoke(ctx, "/service.KeyManager/CreateMnemonic", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *keyManagerClient) CreateKey(ctx context.Context, in *CreateKeyRequest, opts ...grpc.CallOption) (*KeyResponse, error) {
+	out := new(KeyResponse)
+	err := c.cc.Invoke(ctx, "/service.KeyManager/CreateKey", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *keyManagerClient) GetIndex(ctx context.Context, in *GetIndexRequest, opts ...grpc.CallOption) (*KeyResponse, error) {
+	out := new(KeyResponse)
+	err := c.cc.Invoke(ctx, "/service.KeyManager/GetIndex", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *keyManagerClient) GetAddress(ctx context.Context, in *GetAddressRequest, opts ...grpc.CallOption) (*KeyResponse, error) {
+	out := new(KeyResponse)
+	err := c.cc.Invoke(ctx, "/service.KeyManager/GetAddress", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *keyManagerClient) Sign(ctx context.Context, in *SignRequest, opts ...grpc.CallOption) (*SignResponse, error) {
+	out := new(SignResponse)
+	err := c.cc.Invoke(ctx, "/service.KeyManager/Sign", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// KeyManagerServer is the server API for KeyManager service.
+// All implementations must embed UnimplementedKeyManagerServer
+// for forward compatibility
+type KeyManagerServer interface {
+	CreateMnemonic(context.Context, *CreateMnemonicRequest) (*CreateMnemonicResponse, error)
+	CreateKey(context.Context, *CreateKeyRequest) (*KeyResponse, error)
+	GetIndex(context.Context, *GetIndexRequest) (*KeyResponse, error)
+	GetAddress(context.Context, *GetAddressRequest) (*KeyResponse, error)
+	Sign(context.Context, *SignRequest) (*SignResponse, error)
+	mustEmbedUnimplementedKeyManagerServer()
+}
+
+// UnimplementedKeyManagerServer must be embedded to have forward compatible implementations.
+type UnimplementedKeyManagerServer struct {
+}
+
+func (UnimplementedKeyManagerServer) CreateMnemonic(context.Context, *CreateMnemonicRequest) (*CreateMnemonicResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method CreateMnemonic not implemented")
+}
+func (UnimplementedKeyManagerServer) CreateKey(context.Context, *CreateKeyRequest) (*KeyResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method CreateKey not implemented")
+}
+func (UnimplementedKeyManagerServer) GetIndex(context.Context, *GetIndexRequest) (*KeyResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method GetIndex not implemented")
+}
+func (UnimplementedKeyManagerServer) GetAddress(context.Context, *GetAddressRequest) (*KeyResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method GetAddress not implemented")
+}
+func (UnimplementedKeyManagerServer) Sign(context.Context, *SignRequest) (*SignResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method Sign not implemented")
+}
+func (UnimplementedKeyManagerServer) mustEmbedUnimplementedKeyManagerServer() {}
+
+// UnsafeKeyManagerServer may be embedded to opt out of forward compatibility for this service.
+// Use of this interface is not recommended, as added methods to KeyManagerServer will
+// result in compilation errors.
+type UnsafeKeyManagerServer interface {
+	mustEmbedUnimplementedKeyManagerServer()
+}
+
+func RegisterKeyManagerServer(s grpc.ServiceRegistrar, srv KeyManagerServer) {
+	s.RegisterService(&KeyManager_ServiceDesc, srv)
+}
+
+func _KeyManager_CreateMnemonic_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(CreateMnemonicRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(KeyManagerServer).CreateMnemonic(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/service.KeyManager/CreateMnemonic",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(KeyManagerServer).CreateMnemonic(ctx, req.(*CreateMnemonicRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _KeyManager_CreateKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(CreateKeyRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(KeyManagerServer).CreateKey(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/service.KeyManager/CreateKey",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(KeyManagerServer).CreateKey(ctx, req.(*CreateKeyRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _KeyManager_GetIndex_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(GetIndexRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(KeyManagerServer).GetIndex(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/service.KeyManager/GetIndex",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(KeyManagerServer).GetIndex(ctx, req.(*GetIndexRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _KeyManager_GetAddress_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(GetAddressRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(KeyManagerServer).GetAddress(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/service.KeyManager/GetAddress",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(KeyManagerServer).GetAddress(ctx, req.(*GetAddressRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _KeyManager_Sign_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(SignRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(KeyManagerServer).Sign(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/service.KeyManager/Sign",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(KeyManagerServer).Sign(ctx, req.(*SignRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+// KeyManager_ServiceDesc is the grpc.ServiceDesc for KeyManager service.
+// It's only intended for direct use with grpc.RegisterService,
+// and not to be introspected or modified (even as a copy)
+var KeyManager_ServiceDesc = grpc.ServiceDesc{
+	ServiceName: "service.KeyManager",
+	HandlerType: (*KeyManagerServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "CreateMnemonic",
+			Handler:    _KeyManager_CreateMnemonic_Handler,
+		},
+		{
+			MethodName: "CreateKey",
+			Handler:    _KeyManager_CreateKey_Handler,
+		},
+		{
+			MethodName: "GetIndex",
+			Handler:    _KeyManager_GetIndex_Handler,
+		},
+		{
+			MethodName: "GetAddress",
+			Handler:    _KeyManager_GetAddress_Handler,
+		},
+		{
+			MethodName: "Sign",
+			Handler:    _KeyManager_Sign_Handler,
+		},
+	},
+	Streams:  []grpc.StreamDesc{},
+	Metadata: "keyManager.proto",
+}

+ 71 - 0
service/mnemonic.go

@@ -0,0 +1,71 @@
+package service
+
+import (
+	"context"
+	"key-manager/crypto"
+	"key-manager/dao"
+	"key-manager/hd"
+	"key-manager/key"
+)
+
+func (km *KeyManager) CreateMnemonic(ctx context.Context, req *CreateMnemonicRequest) (*CreateMnemonicResponse, error) {
+	km.lk.Lock()
+	defer km.lk.Unlock()
+
+	mType := hd.MnemonicType(req.MnemonicType)
+	if hd.Mnemonic12 != mType && hd.Mnemonic24 != mType {
+		return &CreateMnemonicResponse{
+			Code: mnemonicTypeErrCode,
+			Msg:  mnemonicTypeErrMsg,
+		}, nil
+	}
+
+	if req.Name == "" {
+		return &CreateMnemonicResponse{
+			Code: mnemonicNameErrCode,
+			Msg:  mnemonicNameErrMsg,
+		}, nil
+	}
+
+	log.Infow("CreateMnemonic", "name", req.Name, "mnemonic", mType.String())
+
+	passwordKey := crypto.GenerateEncryptKey([]byte(km.password))
+
+	mnemonic, err := key.GenerateMnemonic(mType)
+	if err != nil {
+		log.Warnw("CreateMnemonic: ", "err", err)
+		return &CreateMnemonicResponse{
+			Code: GenerateMnemonicErrCode,
+			Msg:  err.Error(),
+		}, nil
+	}
+
+	encryptedMnemonic, err := encryptMnemonic(mnemonic, passwordKey)
+	if err != nil {
+		log.Warnw("CreateMnemonic: ", "err", err)
+		return &CreateMnemonicResponse{
+			Code: encryptMnemonicErrCode,
+			Msg:  err.Error(),
+		}, nil
+	}
+
+	err = km.dao.CreateMnemonic(&dao.Mnemonic{
+		Name:     req.Name,
+		Mnemonic: encryptedMnemonic,
+	})
+
+	if err != nil {
+		log.Warnw("CreateMnemonic: ", "err", err)
+		return &CreateMnemonicResponse{
+			Code: dbErrCode,
+			Msg:  err.Error(),
+		}, nil
+	}
+
+	km.mnemonics[req.Name] = mnemonic
+
+	return &CreateMnemonicResponse{
+		Code: okCode,
+		Msg:  okMsg,
+	}, nil
+}

+ 98 - 0
service/service.go

@@ -0,0 +1,98 @@
+package service
+
+import (
+	"crypto/ecdsa"
+	"encoding/hex"
+	"errors"
+	logging "github.com/ipfs/go-log/v2"
+	"key-manager/crypto"
+	"key-manager/dao"
+	"key-manager/hd"
+	"sync"
+)
+
+var log = logging.Logger("key-manager")
+
+type KeyManager struct {
+	UnimplementedKeyManagerServer
+	lk         sync.Mutex
+	dao        *dao.Dao
+	password   string
+	whitelists map[string]struct{}          // key = address
+	mnemonics  map[string]string            // key = name
+	signers    map[string]*ecdsa.PrivateKey // key = address
+}
+
+func NewKeyManager(dao *dao.Dao, password string) (*KeyManager, error) {
+	var km = &KeyManager{
+		UnimplementedKeyManagerServer: UnimplementedKeyManagerServer{},
+		lk:                            sync.Mutex{},
+		dao:                           dao,
+		password:                      password,
+		whitelists:                    map[string]struct{}{},
+		mnemonics:                     map[string]string{},
+		signers:                       map[string]*ecdsa.PrivateKey{},
+	}
+
+	whitelists, err := dao.GetAllWhitelists()
+	if err != nil {
+		return nil, err
+	}
+
+	for _, whitelist := range whitelists {
+		km.whitelists[whitelist.WhiteAddress] = struct{}{}
+		log.Warnf("whitelist address: %s - %s", whitelist.Name, whitelist.WhiteAddress)
+	}
+
+	encryptedMnemonics, err := dao.GetAllMnemonics()
+	if err != nil {
+		return nil, err
+	}
+
+	passwordKey := crypto.GenerateEncryptKey([]byte(password))
+	for _, encryptedMnemonic := range encryptedMnemonics {
+		mnemonic, err := decryptMnemonic(encryptedMnemonic.Mnemonic, passwordKey)
+		if err != nil {
+			log.Errorw("decryptMnemonic failed", "name", encryptedMnemonic.Name, "encryptedMnemonic", encryptedMnemonic.Mnemonic)
+			return nil, err
+		}
+		if ok := hd.CheckMnemonic(mnemonic); !ok {
+			return nil, errors.New("invalid mnemonic")
+		}
+		km.mnemonics[encryptedMnemonic.Name] = mnemonic
+		log.Infow("load mnemonic", "name", encryptedMnemonic.Name)
+	}
+
+	return km, nil
+}
+
+func encryptMnemonic(mnemonic string, key []byte) (string, error) {
+	encryptedMnemonic, err := crypto.Encrypt([]byte(mnemonic), key)
+	if err != nil {
+		return "", err
+	}
+
+	return hex.EncodeToString(encryptedMnemonic), err
+}
+
+func decryptMnemonic(encryptedData string, key []byte) (string, error) {
+	encryptedMnemonic, err := hex.DecodeString(encryptedData)
+	if err != nil {
+		return "", err
+	}
+	mnemonic, err := crypto.Decrypt(encryptedMnemonic, key)
+	if err != nil {
+		return "", err
+	}
+
+	return string(mnemonic), nil
+}
+
+func checkNetwork(network string) error {
+	switch network {
+	case eth, tron:
+		return nil
+	default:
+		return errors.New("network must be eth or tron")
+	}
+}

+ 155 - 0
service/service_test.go

@@ -0,0 +1,155 @@
+package service
+
+import (
+	"encoding/hex"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/types"
+	tronaddr "github.com/fbsobreira/gotron-sdk/pkg/address"
+	"github.com/fbsobreira/gotron-sdk/pkg/client"
+	"google.golang.org/grpc"
+	"key-manager/conf"
+	"key-manager/dao"
+	"math/big"
+	"testing"
+)
+
+func TestTronAddress(t *testing.T) {
+	inputdata := "a9059cbb000000000000000000000000c106d14c008c4e1eb09433c549beff124ddf067300000000000000000000000000000000000000000000000000000001e1f69180"
+	input, err := hex.DecodeString(inputdata)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var methodId [4]byte
+	copy(methodId[:], input[:4])
+	t.Log(hex.EncodeToString(methodId[:]))
+	unpack, err := arguments.Unpack(input[4:])
+	if err != nil {
+		t.Fatal(err)
+	}
+	t.Log(unpack[0].(common.Address))
+	t.Log(unpack[1].(*big.Int))
+	addressTron := make([]byte, 0)
+	addressTron = append(addressTron, tronaddr.TronBytePrefix)
+	addressTron = append(addressTron, unpack[0].(common.Address).Bytes()...)
+	t.Log(tronaddr.Address(addressTron).String())
+
+	addr2, err := tronaddr.Base58ToAddress(tronaddr.Address(addressTron).String())
+	if err != nil {
+		t.Fatal(err)
+	}
+	t.Log("addr2", addr2.String())
+}
+
+func setup() *KeyManager {
+	err := conf.InitConfig("../conf/conf.yaml")
+	if err != nil {
+		panic(err)
+	}
+
+	db, err := dao.InitMysqlDB()
+	if err != nil {
+		panic(err)
+	}
+
+	km, err := NewKeyManager(db, "111")
+	if err != nil {
+		panic(err)
+	}
+
+	return km
+}
+
+func TestKeyManager_Sign_Eth(t *testing.T) {
+	km := setup()
+
+	usdc := common.HexToAddress(ethUsdc)
+	to := common.HexToAddress("0x08eF611f69ab48a31fFF7380088eAa106a3BD5A5")
+	data, err := arguments.Pack(to, big.NewInt(1000000))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	input := append(transferId, data...)
+	tx := &types.DynamicFeeTx{
+		ChainID:   big.NewInt(1),
+		Nonce:     0,
+		GasTipCap: big.NewInt(10000),
+		GasFeeCap: big.NewInt(10000),
+		Gas:       10000,
+		To:        &usdc,
+		Value:     big.NewInt(0),
+		Data:      input,
+	}
+
+	txStr, err := marshalJEthTx(types.NewTx(tx))
+	if err != nil {
+		t.Fatal(err)
+	}
+	t.Log("txStr", txStr)
+
+	signReq := &SignRequest{
+		Network: "eth",
+		Sender:  "0x313A1F70aCA7dE919ff2104F50f94985308E38A8",
+		Tx:      txStr,
+	}
+	res, err := km.Sign(nil, signReq)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	t.Log("signedTx", res.SignedTx)
+	signedTx, err := unmarshalJEthTx(res.SignedTx)
+	if err != nil {
+		t.Fatal(err)
+	}
+	txStr2, err := signedTx.MarshalJSON()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	t.Log("txStr2", string(txStr2))
+}
+
+func TestKeyManager_Sign_Tron(t *testing.T) {
+	km := setup()
+
+	conn := client.NewGrpcClient("grpc.trongrid.io:50051")
+	err := conn.Start(grpc.WithInsecure())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	transactionExtention, err := conn.TRC20Send("TW2aoftrhYE9SbRwyVGFPhDWQU9XzivhBt", "TN1HN14scJCuGMPmr4bh8rxknK5AG2zMgx", tronUsdt, big.NewInt(1000000), 50)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	t.Log("transactionExtention", transactionExtention.String())
+
+	txStr, err := marshalJTronTx(transactionExtention.Transaction)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	t.Log("txStr", txStr)
+
+	signReq := &SignRequest{
+		Network: "tron",
+		Sender:  "TLZMwPhCvqGt8w4523L7ciMcEG4EWwxWKP",
+		Tx:      txStr,
+	}
+
+	res, err := km.Sign(nil, signReq)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	t.Log("signedTx", res.SignedTx)
+
+	tx, err := unmarshalJTronTx(res.SignedTx)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	t.Log("tx", tx.String())
+}

+ 282 - 0
service/signer.go

@@ -0,0 +1,282 @@
+package service
+
+import (
+	"context"
+	"crypto/ecdsa"
+	"crypto/sha256"
+	"encoding/hex"
+	"errors"
+	"fmt"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/crypto"
+	tronaddr "github.com/fbsobreira/gotron-sdk/pkg/address"
+	troncore "github.com/fbsobreira/gotron-sdk/pkg/proto/core"
+	"google.golang.org/protobuf/proto"
+	"key-manager/dao"
+	"math/big"
+)
+
+func format(network, address string) (string, error) {
+	if network == eth {
+		address = common.HexToAddress(address).String()
+	} else {
+
+		addr, err := tronaddr.Base58ToAddress(address)
+		if err != nil {
+			return "", err
+		}
+		address = addr.String()
+	}
+	return address, nil
+}
+
+func (km *KeyManager) loadSigner(network, signer string) error {
+	var err error
+	signer, err = format(network, signer)
+	if err != nil {
+		return err
+	}
+
+	if _, ok := km.signers[signer]; !ok {
+		var info dao.AccountInfo
+		if network == eth {
+			info, err = km.dao.GetEthFromAddress(signer)
+			if err != nil {
+				return err
+			}
+		} else {
+			info, err = km.dao.GetTronFromAddress(signer)
+			if err != nil {
+				return err
+			}
+		}
+
+		if _, exist := km.mnemonics[info.GetName()]; !exist {
+			return errors.New("user mnemonic lost")
+		}
+
+		account, err := createKey(km.mnemonics[info.GetName()], network, info.GetIndex())
+		if err != nil {
+			return err
+		}
+		km.signers[signer] = account.GetPriKey()
+	}
+
+	return nil
+}
+
+func (km *KeyManager) Sign(ctx context.Context, req *SignRequest) (*SignResponse, error) {
+	km.lk.Lock()
+	defer km.lk.Unlock()
+
+	if err := checkNetwork(req.Network); err != nil {
+		return &SignResponse{
+			Code:     networkErrCode,
+			Msg:      networkErrMsg,
+			Network:  req.Network,
+			Sender:   req.Sender,
+			SignedTx: "",
+		}, nil
+	}
+
+	if err := km.loadSigner(req.Network, req.Sender); err != nil {
+		return &SignResponse{
+			Code:     loadSignerErrCode,
+			Msg:      err.Error(),
+			Network:  req.Network,
+			Sender:   req.Sender,
+			SignedTx: "",
+		}, nil
+	}
+
+	var marshalTx string
+	var coin string
+	var to string
+	var amount string
+
+	if req.Network == eth {
+		tx, err := unmarshalJEthTx(req.Tx)
+		if err != nil {
+			return &SignResponse{
+				Code:     unmarshalJEthTxErrCode,
+				Msg:      err.Error(),
+				Network:  req.Network,
+				Sender:   req.Sender,
+				SignedTx: "",
+			}, nil
+		}
+
+		coin, to, amount, err = checkErc(tx, km.whitelists)
+		if err != nil {
+			return &SignResponse{
+				Code:     checkErcTxErrCode,
+				Msg:      err.Error(),
+				Network:  req.Network,
+				Sender:   req.Sender,
+				SignedTx: "",
+			}, nil
+		}
+
+		signedTx, err := signEthTransaction(tx, km.signers[req.Sender])
+		if err != nil {
+			return &SignResponse{
+				Code:     signEthTxErrCode,
+				Msg:      err.Error(),
+				Network:  req.Network,
+				Sender:   req.Sender,
+				SignedTx: "",
+			}, nil
+		}
+
+		marshalTx, err = marshalJEthTx(signedTx)
+		if err != nil {
+			return &SignResponse{
+				Code:     marshalJEthTxErrCode,
+				Msg:      err.Error(),
+				Network:  req.Network,
+				Sender:   req.Sender,
+				SignedTx: "",
+			}, nil
+		}
+	} else {
+		tx, err := unmarshalJTronTx(req.Tx)
+		if err != nil {
+			return &SignResponse{
+				Code:     unmarshalJTronTxErrCode,
+				Msg:      err.Error(),
+				Network:  req.Network,
+				Sender:   req.Sender,
+				SignedTx: "",
+			}, nil
+		}
+
+		coin, to, amount, err = checkTron(tx, km.whitelists)
+		if err != nil {
+			return &SignResponse{
+				Code:     checkTronTxErrCode,
+				Msg:      err.Error(),
+				Network:  req.Network,
+				Sender:   req.Sender,
+				SignedTx: "",
+			}, nil
+		}
+
+		signedTx, err := signTronTransaction(tx, km.signers[req.Sender])
+		if err != nil {
+			return &SignResponse{
+				Code:     signTronTxErrCode,
+				Msg:      err.Error(),
+				Network:  req.Network,
+				Sender:   req.Sender,
+				SignedTx: "",
+			}, nil
+		}
+
+		marshalTx, err = marshalJTronTx(signedTx)
+		if err != nil {
+			return &SignResponse{
+				Code:     marshalJTronTxErrCode,
+				Msg:      err.Error(),
+				Network:  req.Network,
+				Sender:   req.Sender,
+				SignedTx: "",
+			}, nil
+		}
+	}
+
+	err := km.dao.CreateSignTx(&dao.SignTx{
+		Network: req.Network,
+		Coin:    coin,
+		From:    req.Sender,
+		To:      to,
+		Amount:  amount,
+	})
+
+	if err != nil {
+		log.Warnw("save Sign Tx to mysql failed", "network", req.Network, "coin", coin, "from", req.Sender, "to", to, "amount", amount, "err", err)
+	}
+
+	return &SignResponse{
+		Code:     okCode,
+		Msg:      okMsg,
+		Network:  req.Network,
+		Sender:   req.Sender,
+		SignedTx: marshalTx,
+	}, nil
+}
+
+func marshalJEthTx(transaction *types.Transaction) (string, error) {
+	b, err := transaction.MarshalJSON()
+	if err != nil {
+		return "", err
+	}
+
+	return hex.EncodeToString(b), nil
+}
+
+func unmarshalJEthTx(tx string) (*types.Transaction, error) {
+	b, err := hex.DecodeString(tx)
+	if err != nil {
+		return nil, err
+	}
+
+	var transaction = &types.Transaction{}
+	err = transaction.UnmarshalJSON(b)
+	if err != nil {
+		return nil, err
+	}
+
+	return transaction, err
+}
+
+func marshalJTronTx(transaction *troncore.Transaction) (string, error) {
+	b, err := proto.Marshal(transaction)
+	if err != nil {
+		return "", err
+	}
+
+	return hex.EncodeToString(b), nil
+}
+
+func unmarshalJTronTx(tx string) (*troncore.Transaction, error) {
+	b, err := hex.DecodeString(tx)
+	if err != nil {
+		return nil, err
+	}
+
+	var transaction troncore.Transaction
+	err = proto.Unmarshal(b, &transaction)
+	if err != nil {
+		return nil, err
+	}
+
+	return &transaction, nil
+}
+
+func signEthTransaction(transaction *types.Transaction, priv *ecdsa.PrivateKey) (*types.Transaction, error) {
+	signer := types.NewLondonSigner(big.NewInt(1))
+	var err error
+	transaction, err = types.SignTx(transaction, signer, priv)
+	if err != nil {
+		return nil, err
+	}
+
+	return transaction, nil
+}
+
+func signTronTransaction(transaction *troncore.Transaction, priv *ecdsa.PrivateKey) (*troncore.Transaction, error) {
+	rawData, err := proto.Marshal(transaction.GetRawData())
+	if err != nil {
+		return nil, fmt.Errorf("proto marshal tx raw data error: %v", err)
+	}
+	h256h := sha256.New()
+	h256h.Write(rawData)
+	hash := h256h.Sum(nil)
+	signature, err := crypto.Sign(hash, priv)
+	if err != nil {
+		return nil, fmt.Errorf("sign error: %v", err)
+	}
+	transaction.Signature = append(transaction.Signature, signature)
+	return transaction, nil
+}

+ 150 - 0
service/whitelist.go

@@ -0,0 +1,150 @@
+package service
+
+import (
+	"bytes"
+	"encoding/hex"
+	"errors"
+	"fmt"
+	"github.com/ethereum/go-ethereum/accounts/abi"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/types"
+	tronaddr "github.com/fbsobreira/gotron-sdk/pkg/address"
+	troncore "github.com/fbsobreira/gotron-sdk/pkg/proto/core"
+	"google.golang.org/protobuf/reflect/protoreflect"
+	"math/big"
+)
+
+const (
+	ethUsdt  = "0xdAC17F958D2ee523a2206206994597C13D831ec7"
+	ethUsdc  = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
+	tronUsdt = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"
+	tronUsdc = "TEkxiTehnzSmSe2XqrBj4w32RUN966rdz8"
+)
+
+var (
+	transferId, _ = hex.DecodeString("a9059cbb")
+	approveId, _  = hex.DecodeString("095ea7b3")
+)
+
+var (
+	addressTy, _ = abi.NewType("address", "", nil)
+	uint256Ty, _ = abi.NewType("uint256", "", nil)
+	arguments    = abi.Arguments{{Type: addressTy}, {Type: uint256Ty}}
+)
+
+func checkErc(tx *types.Transaction, whitelists map[string]struct{}) (string, string, string, error) {
+	if tx.To().String() == ethUsdt || tx.To().String() == ethUsdc {
+		input := tx.Data()
+		var methodId [4]byte
+		copy(methodId[:], input[:4])
+
+		if !(bytes.Equal(methodId[:], transferId) || bytes.Equal(methodId[:], approveId)) {
+			return "", "", "", errors.New("erc20: only sign transfer and approve")
+		}
+		unpack, err := arguments.Unpack(input[4:])
+		if err != nil {
+			return "", "", "", err
+		}
+
+		to, ok := unpack[0].(common.Address)
+		if !ok {
+			return "", "", "", errors.New("erc20: to parameter error")
+		}
+
+		amount, ok := unpack[1].(*big.Int)
+		if !ok {
+			return "", "", "", errors.New("erc20: amount parameter error")
+		}
+
+		if _, exist := whitelists[to.String()]; !exist {
+			return "", "", "", errors.New("erc20: to is not a whitelist address")
+		}
+
+		coin := usdtCoin
+		if tx.To().String() == ethUsdc {
+			coin = usdcCoin
+		}
+
+		return coin, to.String(), amount.String(), nil
+	} else {
+		if _, exist := whitelists[tx.To().String()]; !exist {
+			return "", "", "", errors.New("eth: to is not a whitelist address")
+		}
+	}
+
+	return ethCoin, tx.To().String(), tx.Value().String(), nil
+}
+
+func checkTron(tx *troncore.Transaction, whitelists map[string]struct{}) (string, string, string, error) {
+	if len(tx.GetRawData().Contract) != 1 {
+		return "", "", "", errors.New("Transaction_Contract length must be 1")
+	}
+	contract := tx.GetRawData().Contract[0]
+
+	var c interface{}
+	switch contract.Type {
+	case troncore.Transaction_Contract_TransferContract:
+		c = &troncore.TransferContract{}
+	case troncore.Transaction_Contract_TriggerSmartContract:
+		c = &troncore.TriggerSmartContract{}
+	default:
+		return "", "", "", errors.New("Transaction_Contract Type must be Transaction_Contract_TransferContract and Transaction_Contract_TriggerSmartContract")
+	}
+
+	if err := contract.GetParameter().UnmarshalTo(c.(protoreflect.ProtoMessage)); err != nil {
+		return "", "", "", fmt.Errorf("proto unmarshal any: %+w", err)
+	}
+
+	if contract.Type == troncore.Transaction_Contract_TransferContract {
+		transferContract := c.(*troncore.TransferContract)
+		to := tronaddr.Address(transferContract.ToAddress)
+		if _, exist := whitelists[to.String()]; !exist {
+			return "", "", "", errors.New("trx: to is not a whitelist address")
+		}
+
+		return trxCoin, to.String(), big.NewInt(0).SetInt64(transferContract.Amount).String(), nil
+	} else {
+		triggerSmartContract := c.(*troncore.TriggerSmartContract)
+		contractAddr := tronaddr.Address(triggerSmartContract.ContractAddress)
+		if contractAddr.String() != tronUsdt && contractAddr.String() != tronUsdc {
+			return "", "", "", errors.New("trc20: only sign usdt and usdc")
+		}
+
+		input := triggerSmartContract.Data
+		var methodId [4]byte
+		copy(methodId[:], input[:4])
+
+		if !(bytes.Equal(methodId[:], transferId) || bytes.Equal(methodId[:], approveId)) {
+			return "", "", "", errors.New("trc20: only sign transfer and approve")
+		}
+
+		unpack, err := arguments.Unpack(input[4:])
+		if err != nil {
+			return "", "", "", err
+		}
+
+		to, ok := unpack[0].(common.Address)
+		if !ok {
+			return "", "", "", errors.New("trc20: to parameter error")
+		}
+
+		amount, ok := unpack[1].(*big.Int)
+		if !ok {
+			return "", "", "", errors.New("erc20: amount parameter error")
+		}
+
+		toTron := make([]byte, 0)
+		toTron = append(toTron, tronaddr.TronBytePrefix)
+		toTron = append(toTron, to.Bytes()...)
+		if _, exist := whitelists[tronaddr.Address(toTron).String()]; !exist {
+			return "", "", "", errors.New("trc20: to is not a whitelist address")
+		}
+
+		coin := usdtCoin
+		if contractAddr.String() == tronUsdc {
+			coin = usdcCoin
+		}
+
+		return coin, to.String(), amount.String(), nil
+	}
+}