diff --git a/api/api_full.go b/api/api_full.go index 1f7c38edf8..f71af54766 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -330,6 +330,10 @@ type FullNode interface { // WalletValidateAddress validates whether a given string can be decoded as a well-formed address WalletValidateAddress(context.Context, string) (address.Address, error) //perm:read + // wallet-security FullNodeExt WalletCustomMethod + // WalletCustomMethod wallet extension operation + WalletCustomMethod(context.Context, WalletMethod, []interface{}) (interface{}, error) //perm:admin + // Other // MethodGroup: Client diff --git a/api/api_wallet.go b/api/api_wallet.go index 973aaaf6d8..daf70af965 100644 --- a/api/api_wallet.go +++ b/api/api_wallet.go @@ -44,4 +44,7 @@ type Wallet interface { WalletExport(context.Context, address.Address) (*types.KeyInfo, error) //perm:admin WalletImport(context.Context, *types.KeyInfo) (address.Address, error) //perm:admin WalletDelete(context.Context, address.Address) error //perm:admin + + // wallet-security WalletExt WalletCustomMethod + WalletCustomMethod(context.Context, WalletMethod, []interface{}) (interface{}, error) //perm:admin } diff --git a/api/api_wallet_ext.go b/api/api_wallet_ext.go new file mode 100644 index 0000000000..aae18de734 --- /dev/null +++ b/api/api_wallet_ext.go @@ -0,0 +1,44 @@ +package api + +import ( + "github.com/filecoin-project/go-address" +) + +// wallet-security AddrListEncrypt stuct +type AddrListEncrypt struct { + Addr address.Address + Encrypt bool +} +type WalletMethod int64 + +const ( + Unknown WalletMethod = 0 + WalletListForEnc WalletMethod = 1 + WalletExportForEnc WalletMethod = 2 + WalletDeleteForEnc WalletMethod = 3 + WalletAddPasswd WalletMethod = 4 + WalletResetPasswd WalletMethod = 5 + WalletClearPasswd WalletMethod = 6 + WalletCheckPasswd WalletMethod = 7 + WalletEncrypt WalletMethod = 8 + WalletDecrypt WalletMethod = 9 + WalletIsEncrypt WalletMethod = 10 +) + +var WalletMethodStr = map[WalletMethod]string{ + Unknown: "Unknown", + WalletListForEnc: "WalletListForEnc", + WalletExportForEnc: "WalletExportForEnc", + WalletDeleteForEnc: "WalletDeleteForEnc", + WalletAddPasswd: "WalletAddPasswd", + WalletResetPasswd: "WalletResetPasswd", + WalletClearPasswd: "WalletClearPasswd", + WalletCheckPasswd: "WalletCheckPasswd", + WalletEncrypt: "WalletEncrypt", + WalletDecrypt: "WalletDecrypt", + WalletIsEncrypt: "WalletIsEncrypt", +} + +func (w WalletMethod) String() string { + return WalletMethodStr[w] +} diff --git a/api/docgen/docgen.go b/api/docgen/docgen.go index 0e97997a6d..4a991c57f5 100644 --- a/api/docgen/docgen.go +++ b/api/docgen/docgen.go @@ -482,6 +482,11 @@ func ExampleValue(method string, t, parent reflect.Type) interface{} { } case reflect.Interface: return struct{}{} + // wallet-security fix docgen build err + case reflect.Int64: + return 0 + case reflect.Map: + return make(map[string]string) } panic(fmt.Sprintf("No example value for type: %s (method '%s')", t, method)) diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go index 370a213a0c..fc1bf586c6 100644 --- a/api/mocks/mock_full.go +++ b/api/mocks/mock_full.go @@ -4128,6 +4128,22 @@ func (mr *MockFullNodeMockRecorder) WalletVerify(arg0, arg1, arg2, arg3 interfac return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletVerify", reflect.TypeOf((*MockFullNode)(nil).WalletVerify), arg0, arg1, arg2, arg3) } +// wallet-security MockFullNode WalletCustomMethod +// WalletCustomMethod mocks base method +func (m *MockFullNode) WalletCustomMethod(arg0 context.Context, arg1 api.WalletMethod, arg2 []interface{}) (interface{}, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletCustomMethod", arg0, arg1, arg2) + ret0, _ := ret[0].(interface{}) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WalletCustomMethod indicates an expected call of WalletCustomMethod +func (mr *MockFullNodeMockRecorder) WalletCustomMethod(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletCustomMethod", reflect.TypeOf((*MockFullNode)(nil).WalletCustomMethod), arg0, arg1, arg2) +} + // Web3ClientVersion mocks base method. func (m *MockFullNode) Web3ClientVersion(arg0 context.Context) (string, error) { m.ctrl.T.Helper() diff --git a/api/proxy_gen.go b/api/proxy_gen.go index 70579af1a4..5d217d8e34 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -608,6 +608,9 @@ type FullNodeMethods struct { WalletVerify func(p0 context.Context, p1 address.Address, p2 []byte, p3 *crypto.Signature) (bool, error) `perm:"read"` + // wallet-security FullNodeStructExt WalletCustomMethod + WalletCustomMethod func(p0 context.Context, p1 WalletMethod, p2 []interface{}) (interface{}, error) `perm:"admin"` + Web3ClientVersion func(p0 context.Context) (string, error) `perm:"read"` } @@ -1150,6 +1153,9 @@ type WalletMethods struct { WalletNew func(p0 context.Context, p1 types.KeyType) (address.Address, error) `perm:"admin"` WalletSign func(p0 context.Context, p1 address.Address, p2 []byte, p3 MsgMeta) (*crypto.Signature, error) `perm:"admin"` + + // wallet-security WalletStructExt WalletCustomMethod + WalletCustomMethod func(p0 context.Context, p1 WalletMethod, p2 []interface{}) (interface{}, error) `perm:"admin"` } type WalletStub struct { @@ -4021,6 +4027,16 @@ func (s *FullNodeStub) WalletVerify(p0 context.Context, p1 address.Address, p2 [ return false, ErrNotSupported } +func (s *FullNodeStruct) WalletCustomMethod(p0 context.Context, p1 WalletMethod, p2 []interface{}) (interface{}, error) { + if s.Internal.WalletCustomMethod == nil { + return nil, ErrNotSupported + } + return s.Internal.WalletCustomMethod(p0, p1, p2) +} +func (s *FullNodeStub) WalletCustomMethod(p0 context.Context, p1 WalletMethod, p2 []interface{}) (interface{}, error) { + return nil, ErrNotSupported +} + func (s *FullNodeStruct) Web3ClientVersion(p0 context.Context) (string, error) { if s.Internal.Web3ClientVersion == nil { return "", ErrNotSupported @@ -6705,6 +6721,17 @@ func (s *WalletStub) WalletSign(p0 context.Context, p1 address.Address, p2 []byt return nil, ErrNotSupported } +// wallet-security WalletStruct WalletCustomMethod +func (s *WalletStruct) WalletCustomMethod(p0 context.Context, p1 WalletMethod, p2 []interface{}) (interface{}, error) { + if s.Internal.WalletCustomMethod == nil { + return nil, ErrNotSupported + } + return s.Internal.WalletCustomMethod(p0, p1, p2) +} +func (s *WalletStub) WalletCustomMethod(p0 context.Context, p1 WalletMethod, p2 []interface{}) (interface{}, error) { + return nil, ErrNotSupported +} + func (s *WorkerStruct) AddPiece(p0 context.Context, p1 storiface.SectorRef, p2 []abi.UnpaddedPieceSize, p3 abi.UnpaddedPieceSize, p4 storiface.Data) (storiface.CallID, error) { if s.Internal.AddPiece == nil { return *new(storiface.CallID), ErrNotSupported diff --git a/api/v0api/full.go b/api/v0api/full.go index 490cd73c83..29a84b822d 100644 --- a/api/v0api/full.go +++ b/api/v0api/full.go @@ -305,6 +305,10 @@ type FullNode interface { // WalletValidateAddress validates whether a given string can be decoded as a well-formed address WalletValidateAddress(context.Context, string) (address.Address, error) //perm:read + // wallet-security FullNodeExt WalletCustomMethod + // WalletCustomMethod wallet extension operation + WalletCustomMethod(context.Context, api.WalletMethod, []interface{}) (interface{}, error) //perm:admin + // Other // MethodGroup: Client diff --git a/api/v0api/proxy_gen.go b/api/v0api/proxy_gen.go index 17a1ae84ae..b1a88c5416 100644 --- a/api/v0api/proxy_gen.go +++ b/api/v0api/proxy_gen.go @@ -418,6 +418,10 @@ type FullNodeMethods struct { WalletValidateAddress func(p0 context.Context, p1 string) (address.Address, error) `perm:"read"` WalletVerify func(p0 context.Context, p1 address.Address, p2 []byte, p3 *crypto.Signature) (bool, error) `perm:"read"` + + // wallet-security FullNodeStructExt WalletCustomMethod + WalletCustomMethod func(p0 context.Context, p1 api.WalletMethod, p2 []interface{}) (interface{}, error) `perm:"admin"` + } type FullNodeStub struct { @@ -2575,6 +2579,16 @@ func (s *FullNodeStub) WalletVerify(p0 context.Context, p1 address.Address, p2 [ return false, ErrNotSupported } +func (s *FullNodeStruct) WalletCustomMethod(p0 context.Context, p1 api.WalletMethod, p2 []interface{}) (interface{}, error) { + if s.Internal.WalletCustomMethod == nil { + return nil, ErrNotSupported + } + return s.Internal.WalletCustomMethod(p0, p1, p2) +} +func (s *FullNodeStub) WalletCustomMethod(p0 context.Context, p1 api.WalletMethod, p2 []interface{}) (interface{}, error) { + return nil, ErrNotSupported +} + func (s *GatewayStruct) ChainGetBlockMessages(p0 context.Context, p1 cid.Cid) (*api.BlockMessages, error) { if s.Internal.ChainGetBlockMessages == nil { return nil, ErrNotSupported diff --git a/api/v0api/v0mocks/mock_full.go b/api/v0api/v0mocks/mock_full.go index 619f19d35b..311471a7a3 100644 --- a/api/v0api/v0mocks/mock_full.go +++ b/api/v0api/v0mocks/mock_full.go @@ -3380,3 +3380,19 @@ func (mr *MockFullNodeMockRecorder) WalletVerify(arg0, arg1, arg2, arg3 interfac mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletVerify", reflect.TypeOf((*MockFullNode)(nil).WalletVerify), arg0, arg1, arg2, arg3) } + +// wallet-security MockFullNode WalletCustomMethod +// WalletCustomMethod mocks base method +func (m *MockFullNode) WalletCustomMethod(arg0 context.Context, arg1 api.WalletMethod, arg2 []interface{}) (interface{}, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletCustomMethod", arg0, arg1, arg2) + ret0, _ := ret[0].(interface{}) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WalletCustomMethod indicates an expected call of WalletCustomMethod +func (mr *MockFullNodeMockRecorder) WalletCustomMethod(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletCustomMethod", reflect.TypeOf((*MockFullNode)(nil).WalletCustomMethod), arg0, arg1, arg2) +} diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 1dfe553d5a..c68072b2f0 100644 Binary files a/build/openrpc/full.json.gz and b/build/openrpc/full.json.gz differ diff --git a/chain/wallet/key/key.go b/chain/wallet/key/key.go index 4220666108..c7c6eb985e 100644 --- a/chain/wallet/key/key.go +++ b/chain/wallet/key/key.go @@ -20,9 +20,16 @@ func GenerateKey(typ types.KeyType) (*Key, error) { if err != nil { return nil, err } + + // wallet-security 加密 + pk1, err := MakeByte(pk) + if err != nil { + return nil, err + } + ki := types.KeyInfo{ Type: typ, - PrivateKey: pk, + PrivateKey: pk1, //PrivateKey: pk, } return NewKey(ki) } @@ -39,8 +46,15 @@ func NewKey(keyinfo types.KeyInfo) (*Key, error) { KeyInfo: keyinfo, } - var err error - k.PublicKey, err = sigs.ToPublic(ActSigType(k.Type), k.PrivateKey) + // wallet-security 解密 + // var err error + // k.PublicKey, err = sigs.ToPublic(ActSigType(k.Type), k.PrivateKey) + pk, err := UnMakeByte(k.PrivateKey) + if err != nil { + return nil, err + } + k.PublicKey, err = sigs.ToPublic(ActSigType(k.Type), pk) + if err != nil { return nil, err } diff --git a/chain/wallet/key/key_ext.go b/chain/wallet/key/key_ext.go new file mode 100644 index 0000000000..d349aa0719 --- /dev/null +++ b/chain/wallet/key/key_ext.go @@ -0,0 +1,104 @@ +package key + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/hex" + "io" + + "golang.org/x/xerrors" +) + +var AddrPrefix = []byte{0xff, 0xff, 0xff, 0xff, 0xff} // addrPrefix = "/////" +var WalletPasswd string = "" +var PasswdPath string = "" + +// wallet-security AESEncrypt/AESDecrypt +// IsSetup check setup password for wallet +func IsSetup() bool { + return PasswdPath != "" +} + +// IsLock check setup lock for wallet +func IsLock() bool { + return WalletPasswd == "" +} + +func AESEncrypt(key, plaintext []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, xerrors.Errorf("passwd must 6 to 16 characters") + } + + ciphertext := make([]byte, aes.BlockSize+len(plaintext)) + iv := ciphertext[:aes.BlockSize] + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return nil, err + } + + stream := cipher.NewCFBEncrypter(block, iv) + stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext) + + return ciphertext, nil +} + +func AESDecrypt(key, ciphertext []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, xerrors.Errorf("passwd must 6 to 16 characters") + } else if len(ciphertext) < aes.BlockSize { + return nil, xerrors.Errorf("passwd must 6 to 16 characters") + } + + iv := ciphertext[:aes.BlockSize] + ciphertext = ciphertext[aes.BlockSize:] + stream := cipher.NewCFBDecrypter(block, iv) + stream.XORKeyStream(ciphertext, ciphertext) + + return ciphertext, nil +} + +func UnMakeByte(pk []byte) ([]byte, error) { + if !IsSetup() { + return pk, nil + } + + if !bytes.Equal(pk[:4], AddrPrefix) { + return pk, nil + } else if !IsLock() { + msg := make([]byte, len(pk)-4) + copy(msg, pk[4:]) + m5_passwd, _ := hex.DecodeString(WalletPasswd) + return AESDecrypt(m5_passwd, msg) + } + return nil, xerrors.Errorf("wallet is lock") +} + +func MakeByte(pk []byte) ([]byte, error) { + if !IsSetup() { + return pk, nil + } + + if IsLock() { + return nil, xerrors.Errorf("wallet is lock") + } + + m5_passwd, _ := hex.DecodeString(WalletPasswd) + msg, err := AESEncrypt(m5_passwd, pk) + if err != nil { + return nil, err + } + text := make([]byte, len(msg)+4) + copy(text[:4], AddrPrefix) + copy(text[4:], msg) + return text, nil +} + +func IsPrivateKeyEnc(pk []byte) bool { + if !IsSetup() || !bytes.Equal(pk[:4], AddrPrefix) { + return false + } + return true +} diff --git a/chain/wallet/ledger/ledger.go b/chain/wallet/ledger/ledger.go index 5279389de8..7254505588 100644 --- a/chain/wallet/ledger/ledger.go +++ b/chain/wallet/ledger/ledger.go @@ -227,6 +227,12 @@ func (lw LedgerWallet) WalletNew(ctx context.Context, t types.KeyType) (address. return lw.importKey(ctx, lki) } +// wallet-security LedgerWallet WalletCustomMethod +// WalletCustomMethod dont use this method for LedgerWallet +func (lw *LedgerWallet) WalletCustomMethod(ctx context.Context, meth api.WalletMethod, args []interface{}) (interface{}, error) { + return nil, xerrors.Errorf("LedgerWallet do not support extension operations for the time being") +} + func (lw *LedgerWallet) Get() api.Wallet { if lw == nil { return nil diff --git a/chain/wallet/multi_ext.go b/chain/wallet/multi_ext.go new file mode 100644 index 0000000000..48e61ea16e --- /dev/null +++ b/chain/wallet/multi_ext.go @@ -0,0 +1,116 @@ +package wallet + +import ( + "context" + + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/types" +) + +// wallet-security MultiWallet func +// add MultiWallet Support extended +func (m MultiWallet) WalletListEncrypt(ctx context.Context) ([]api.AddrListEncrypt, error) { + out := make([]api.AddrListEncrypt, 0) + ws := nonNil(m.Remote, m.Ledger, m.Local) + for _, w := range ws { + if w == m.Local.Get() { + l, err := m.Local.WalletListEncrypt(ctx) + if err != nil { + return nil, err + } + out = append(out, l...) + } else { + l, err := w.WalletList(ctx) + if err != nil { + return nil, err + } + for _, v := range l { + out = append(out, api.AddrListEncrypt{ + Addr: v, + Encrypt: false, + }) + } + } + } + return out, nil +} +func (m MultiWallet) WalletExportEncrypt(ctx context.Context, address address.Address, passwd string) (*types.KeyInfo, error) { + w, err := m.find(ctx, address, m.Remote, m.Local) + if err != nil { + return nil, err + } + if w == nil { + return nil, xerrors.Errorf("key not found") + } + if w == m.Local.Get() { + return m.Local.WalletExportEncrypt(ctx, address, passwd) + } + return w.WalletExport(ctx, address) +} +func (m MultiWallet) WalletDeleteEncrypt(ctx context.Context, address address.Address, passwd string) error { + w, err := m.find(ctx, address, m.Remote, m.Ledger, m.Local) + if err != nil { + return err + } + if w == nil { + return nil + } + if w == m.Local.Get() { + return m.Local.WalletDeleteEncrypt(ctx, address, passwd) + } + return w.WalletDelete(ctx, address) +} + +// wallet-security MultiWallet WalletCustomMethod +// WalletCustomMethod dont use this method for MultiWallet +func (m MultiWallet) WalletCustomMethod(ctx context.Context, meth api.WalletMethod, args []interface{}) (interface{}, error) { + switch meth { + case api.WalletListForEnc: + return m.WalletListEncrypt(ctx) + case api.WalletExportForEnc: + if len(args) < 2 { + return nil, xerrors.Errorf("args must is 2 for exec method, but get args is %v", len(args)) + } + addr_str := args[0].(string) + addr, _ := address.NewFromString(addr_str) + passwd := args[1].(string) + return m.WalletExportEncrypt(ctx, addr, passwd) + case api.WalletDeleteForEnc: + if len(args) < 2 { + return nil, xerrors.Errorf("args must is 2 for exec method, but get args is %v", len(args)) + } + addr_str := args[0].(string) + addr, _ := address.NewFromString(addr_str) + passwd := args[1].(string) + return nil, m.WalletDeleteEncrypt(ctx, addr, passwd) + case api.WalletEncrypt: + if len(args) < 1 { + return nil, xerrors.Errorf("args must is 1 for exec method, but get args is %v", len(args)) + } + addr_str := args[0].(string) + addr, _ := address.NewFromString(addr_str) + return m.Local.WalletEncrypt(ctx, addr) + case api.WalletDecrypt: + if len(args) < 2 { + return nil, xerrors.Errorf("args must is 2 for exec method, but get args is %v", len(args)) + } + addr_str := args[0].(string) + addr, _ := address.NewFromString(addr_str) + passwd := args[1].(string) + return m.Local.WalletDecrypt(ctx, addr, passwd) + case api.WalletIsEncrypt: + if len(args) < 1 { + return nil, xerrors.Errorf("args must is 1 for exec method, but get args is %v", len(args)) + } + addr_str := args[0].(string) + addr, _ := address.NewFromString(addr_str) + return m.Local.WalletIsEncrypt(ctx, addr) + } + return m.Local.WalletCustomMethod(ctx, meth, args) +} + +var _ api.Wallet = MultiWallet{} diff --git a/chain/wallet/wallet.go b/chain/wallet/wallet.go index 76af663c78..f6f94e5a52 100644 --- a/chain/wallet/wallet.go +++ b/chain/wallet/wallet.go @@ -70,7 +70,13 @@ func (w *LocalWallet) WalletSign(ctx context.Context, addr address.Address, msg return nil, xerrors.Errorf("signing using key '%s': %w", addr.String(), types.ErrKeyInfoNotFound) } - return sigs.Sign(key.ActSigType(ki.Type), ki.PrivateKey, msg) + // wallet-security 解密 + // return sigs.Sign(key.ActSigType(ki.Type), ki.PrivateKey, msg) + pk, err := key.UnMakeByte(ki.PrivateKey) + if err != nil { + return nil, err + } + return sigs.Sign(key.ActSigType(ki.Type), pk, msg) } func (w *LocalWallet) findKey(addr address.Address) (*key.Key, error) { @@ -144,6 +150,12 @@ func (w *LocalWallet) WalletExport(ctx context.Context, addr address.Address) (* return nil, xerrors.Errorf("key not found for %s", addr) } + // wallet-security wallet加密判断 + pk := k.PrivateKey + if pk[0] == 0xff && pk[1] == 0xff && pk[2] == 0xff && pk[3] == 0xff { + return nil, xerrors.Errorf("The wallet is encrypted and there is no export permission.") + } + return &k.KeyInfo, nil } @@ -156,6 +168,13 @@ func (w *LocalWallet) WalletImport(ctx context.Context, ki *types.KeyInfo) (addr return address.Undef, xerrors.Errorf("failed to make key: %w", err) } + // wallet-security 加密 + pk, err := key.MakeByte(k.PrivateKey) + if err != nil { + return address.Undef, err + } + k.PrivateKey = pk + if err := w.keystore.Put(KNamePrefix+k.Address.String(), k.KeyInfo); err != nil { return address.Undef, xerrors.Errorf("saving to keystore: %w", err) } @@ -320,6 +339,16 @@ func (w *LocalWallet) deleteDefault() { } func (w *LocalWallet) WalletDelete(ctx context.Context, addr address.Address) error { + // wallet-security wallet加密判断 + key, err := w.findKey(addr) + if err != nil { + return err + } + pk := key.PrivateKey + if pk[0] == 0xff && pk[1] == 0xff && pk[2] == 0xff && pk[3] == 0xff { + return xerrors.Errorf("The wallet is encrypted and there is no delete permission.") + } + if err := w.walletDelete(ctx, addr); err != nil { return xerrors.Errorf("wallet delete: %w", err) } diff --git a/chain/wallet/wallet_aes.go b/chain/wallet/wallet_aes.go new file mode 100644 index 0000000000..5236f61f95 --- /dev/null +++ b/chain/wallet/wallet_aes.go @@ -0,0 +1,230 @@ +package wallet + +import ( + /* #nosec G501 */ + "crypto/md5" + "encoding/hex" + "fmt" + "io/ioutil" + "os" + "regexp" + + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/wallet/key" + "golang.org/x/xerrors" +) + +var substitutePwd = []byte("****************") + +const walletSaltPwd string = "8XBMT5OruN6XwEhaYfJfMJPL3WUhjWmH" +const walletCheckMsg string = "check passwd is success" + +type KeyInfo struct { + types.KeyInfo + Enc bool +} + +func completionPwd(pwd []byte) []byte { + sub := 16 - len(pwd) + if sub > 0 { + pwd = append(pwd, substitutePwd[:sub]...) + } + return pwd +} + +func SetupPasswd(passwd []byte, path string) error { + _, err := os.Stat(path) + if err == nil { + return xerrors.Errorf("checking file before Setup passwd '%s': file already exists", path) + } else if !os.IsNotExist(err) { + return xerrors.Errorf("checking file before Setup passwd '%s': %w", path, err) + } + + //用户密码加密check消息 + passwd = completionPwd(passwd) + /* #nosec G401 */ + m5 := md5.Sum(passwd) + checkmsg, err := key.AESEncrypt(m5[:], []byte(walletCheckMsg)) + m5passwd := hex.EncodeToString(m5[:]) + if err != nil { + return err + } + + //用户密码用盐密加密 + saltkey := completionPwd([]byte(walletSaltPwd)) + /* #nosec G401 */ + saltm5 := md5.Sum(saltkey) + saltm5passwdmsg, err := key.AESEncrypt(saltm5[:], []byte(m5passwd)) + //saltm5passwd := hex.EncodeToString(saltm5[:]) + if err != nil { + return err + } + + //存到/passwd + savetext := make([]byte, 64+len(checkmsg)) + copy(savetext[:64], saltm5passwdmsg) + copy(savetext[64:], checkmsg) + err = ioutil.WriteFile(path, savetext, 0600) + if err != nil { + return xerrors.Errorf("writing file '%s': %w", path, err) + } + + //密码保存到内存 + key.WalletPasswd = m5passwd + key.PasswdPath = path + + return nil +} + +func ResetPasswd(passwd []byte) error { + err := os.Remove(key.PasswdPath) + if err != nil { + return err + } + + err = SetupPasswd(passwd, key.PasswdPath) + if err != nil { + return err + } + + return nil +} + +func ClearPasswd() error { + err := os.Remove(key.PasswdPath) + if err != nil { + return err + } + key.WalletPasswd = "" + key.PasswdPath = "" + return nil +} + +func CheckPasswd(passwd []byte) error { + fstat, err := os.Stat(key.PasswdPath) + if os.IsNotExist(err) { + return fmt.Errorf("opening file '%s': file info not found", key.PasswdPath) + } else if err != nil { + return fmt.Errorf("opening file '%s': %w", key.PasswdPath, err) + } + + if fstat.Mode()&0077 != 0 { + return fmt.Errorf("permissions of key: '%s' are too relaxed, required: 0600, got: %#o", key.PasswdPath, fstat.Mode()) + } + + if fstat.Mode()&0077 != 0 { + return xerrors.Errorf("permissions of key: '%s' are too relaxed, required: 0600, got: %#o", key.PasswdPath, fstat.Mode()) + } + + file, err := os.Open(key.PasswdPath) + if err != nil { + return xerrors.Errorf("opening file '%s': %w", key.PasswdPath, err) + } + defer file.Close() //nolint:errcheck + + data, err := ioutil.ReadAll(file) + if err != nil { + return xerrors.Errorf("reading file '%s': %w", key.PasswdPath, err) + } + + //读取加密check消息 + passwd = completionPwd(passwd) + /* #nosec G401 */ + m5 := md5.Sum(passwd) + text, err := key.AESDecrypt(m5[:], data[64:]) + if err != nil { + return err + } + + //验证check消息 + str := string(text) + if walletCheckMsg != str { + return xerrors.Errorf("check passwd is failed") + } + //密码保存到内存 + if key.IsLock() { + key.WalletPasswd = hex.EncodeToString(m5[:]) + } + return nil +} + +func GetSetupState(path string) bool { + fstat, err := os.Stat(path) + if os.IsNotExist(err) { + return false + } else if err != nil { + return false + } + + if fstat.Mode()&0077 != 0 { + return false + } + + file, err := os.Open(path) + if err != nil { + log.Infof("opening file '%s': %w", path, err) + return false + } + defer file.Close() //nolint:errcheck + + data, err := ioutil.ReadAll(file) + if err != nil { + log.Infof("reading file '%s': %w", path, err) + return false + } + + //读取密码 + saltkey := completionPwd([]byte(walletSaltPwd)) + /* #nosec G401 */ + saltm5 := md5.Sum(saltkey) + m5passwd, err := key.AESDecrypt(saltm5[:], data[:64]) + if err != nil { + log.Infof("err: %v", err) + return false + } + + //用读取密码去验证加密check消息 + m5pwdstr := string(m5passwd[:32]) + m5pwd, _ := hex.DecodeString(m5pwdstr) + text, err := key.AESDecrypt(m5pwd[:16], data[64:]) + if err != nil { + log.Infof("err: %v", err) + return false + } + str := string(text) + if walletCheckMsg != str { + log.Infof("check passwd is failed") + return false + } + + key.PasswdPath = path + key.WalletPasswd = m5pwdstr + return true +} + +// GetSetupStateForLocal only lotus-wallet use +// +// check encryption status +func GetSetupStateForLocal(path string) bool { + fstat, err := os.Stat(path) + if os.IsNotExist(err) { + return false + } else if err != nil { + return false + } + + if fstat.Mode()&0077 != 0 { + return false + } + + return true +} + +func RegexpPasswd(passwd string) error { + // if ok, _ := regexp.MatchString(`^[a-zA-Z].{5,15}`, passwd); len(passwd) > 16 || !ok { + if ok, _ := regexp.MatchString(`^[a-zA-Z].[0-9A-Za-z!@#$%^&*]{4,15}`, passwd); len(passwd) > 16 || !ok { + // if ok, _ := regexp.MatchString(`^(?![0-9]+$)(?![a-zA-Z]+$)[a-zA-Z][0-9A-Za-z!@#$%^&*]{5,15}$`, passwd); len(passwd) > 18 || !ok { + return fmt.Errorf("invalid password format. (The beginning of the letter, 6 to 16 characters.)") + } + return nil +} diff --git a/chain/wallet/wallet_common.go b/chain/wallet/wallet_common.go new file mode 100644 index 0000000000..f98cfd1e2c --- /dev/null +++ b/chain/wallet/wallet_common.go @@ -0,0 +1,51 @@ +package wallet + +import ( + "bufio" + "fmt" + "os" + "strings" +) + +/* +#cgo LDFLAGS: -L${SRCDIR}/ +#include +#include +struct termios disable_echo() { + struct termios of, nf; + tcgetattr(fileno(stdin), &of); + nf = of; + nf.c_lflag &= ~ECHO; + nf.c_lflag |= ECHONL; + if (tcsetattr(fileno(stdin), TCSANOW, &nf) != 0) { + perror("tcsetattr"); + } + return of; +} +void restore_echo(struct termios f) { + if (tcsetattr(fileno(stdin), TCSANOW, &f) != 0) { + perror("tcsetattr"); + } +} +*/ +import "C" + +func Prompt(msg string) string { + fmt.Printf("%s", msg) + oldFlags := C.disable_echo() + passwd, err := bufio.NewReader(os.Stdin).ReadString('\n') + C.restore_echo(oldFlags) + if err != nil { + panic(err) + } + return strings.TrimSpace(passwd) +} + +func PromptSimple(msg string) string { + fmt.Printf("%s", msg) + passwd, err := bufio.NewReader(os.Stdin).ReadString('\n') + if err != nil { + panic(err) + } + return strings.TrimSpace(passwd) +} diff --git a/chain/wallet/wallet_ext.go b/chain/wallet/wallet_ext.go new file mode 100644 index 0000000000..0f83a6e7c3 --- /dev/null +++ b/chain/wallet/wallet_ext.go @@ -0,0 +1,566 @@ +package wallet + +import ( + "bytes" + "context" + "fmt" + "sort" + "strings" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/wallet/key" + "golang.org/x/xerrors" +) + +// wallet-security LocalWallet WalletCustomMethod +func (w *LocalWallet) WalletCustomMethod(ctx context.Context, meth api.WalletMethod, args []interface{}) (interface{}, error) { + + switch meth { + case api.Unknown: + return nil, xerrors.Errorf("exec method is unknown") + case api.WalletListForEnc: + return w.WalletListEncrypt(ctx) + case api.WalletExportForEnc: + if len(args) < 2 { + return nil, xerrors.Errorf("args must is 2 for exec method, but get args is %v", len(args)) + } + addr := args[0].(address.Address) + passwd := args[1].(string) + return w.WalletExportEncrypt(ctx, addr, passwd) + case api.WalletDeleteForEnc: + if len(args) < 2 { + return nil, xerrors.Errorf("args must is 2 for exec method, but get args is %v", len(args)) + } + addr := args[0].(address.Address) + passwd := args[1].(string) + return nil, w.WalletDeleteEncrypt(ctx, addr, passwd) + case api.WalletAddPasswd: + if len(args) < 2 { + return nil, xerrors.Errorf("args must is 2 for exec method, but get args is %v", len(args)) + } + passwd := args[0].(string) + path := args[1].(string) + return nil, w.WalletAddPasswd(ctx, passwd, path) + case api.WalletResetPasswd: + if len(args) < 2 { + return nil, xerrors.Errorf("args must is 2 for exec method, but get args is %v", len(args)) + } + oldPasswd := args[0].(string) + newPasswd := args[1].(string) + return w.WalletResetPasswd(ctx, oldPasswd, newPasswd) + case api.WalletClearPasswd: + if len(args) < 1 { + return nil, xerrors.Errorf("args must is 1 for exec method, but get args is %v", len(args)) + } + passwd := args[0].(string) + return w.WalletClearPasswd(ctx, passwd) + case api.WalletCheckPasswd: + if len(args) < 1 { + return nil, xerrors.Errorf("args must is 1 for exec method, but get args is %v", len(args)) + } + passwd := args[0].(string) + return w.WalletCheckPasswd(ctx, passwd), nil + case api.WalletEncrypt: + if len(args) < 1 { + return nil, xerrors.Errorf("args must is 1 for exec method, but get args is %v", len(args)) + } + addr := args[0].(address.Address) + return w.WalletEncrypt(ctx, addr) + case api.WalletDecrypt: + if len(args) < 2 { + return nil, xerrors.Errorf("args must is 2 for exec method, but get args is %v", len(args)) + } + addr := args[0].(address.Address) + passwd := args[1].(string) + return w.WalletDecrypt(ctx, addr, passwd) + case api.WalletIsEncrypt: + if len(args) < 1 { + return nil, xerrors.Errorf("args must is 1 for exec method, but get args is %v", len(args)) + } + addr := args[0].(address.Address) + // passwd := args[1].(string) + return w.WalletIsEncrypt(ctx, addr) + default: + return nil, xerrors.Errorf("exec method is unknown") + } +} + +// wallet-security LocalWallet func +func (w *LocalWallet) WalletListEncrypt(context.Context) ([]api.AddrListEncrypt, error) { + all, err := w.keystore.List() + if err != nil { + return nil, xerrors.Errorf("listing keystore: %w", err) + } + + sort.Strings(all) + + seen := map[address.Address]struct{}{} + out := make([]api.AddrListEncrypt, 0, len(all)) + for _, a := range all { + var addr address.Address + var err error + if strings.HasPrefix(a, KNamePrefix) { + name := strings.TrimPrefix(a, KNamePrefix) + addr, err = address.NewFromString(name) + if err != nil { + return nil, xerrors.Errorf("converting name to address: %w", err) + } + + if _, ok := seen[addr]; ok { + continue // got duplicate with a different prefix + } + seen[addr] = struct{}{} + + key, err := w.findKey(addr) + if err != nil { + return nil, err + } + pk := key.PrivateKey + var encrypt bool + if pk[0] == 0xff && pk[1] == 0xff && pk[2] == 0xff && pk[3] == 0xff { + encrypt = true + } + + out = append(out, api.AddrListEncrypt{Addr: addr, Encrypt: encrypt}) + } + } + + sort.Slice(out, func(i, j int) bool { + return out[i].Addr.String() < out[j].Addr.String() + }) + + return out, nil +} + +func (w *LocalWallet) WalletExportEncrypt(ctx context.Context, addr address.Address, passwd string) (*types.KeyInfo, error) { + if key.IsSetup() { + if err := CheckPasswd([]byte(passwd)); err != nil { + return nil, err + } + } + + k, err := w.findKey(addr) + if err != nil { + return nil, xerrors.Errorf("failed to find key to export: %w", err) + } + if k == nil { + return nil, xerrors.Errorf("key not found") + } + + // wallet-security 解密 + pk, err := key.UnMakeByte(k.PrivateKey) + if err != nil { + return nil, err + } + var pki types.KeyInfo + pki.Type = k.Type + pki.PrivateKey = pk + return &pki, nil +} + +func (w *LocalWallet) WalletDeleteEncrypt(ctx context.Context, addr address.Address, passwd string) error { + if key.IsSetup() { + if err := CheckPasswd([]byte(passwd)); err != nil { + return err + } + } + + if err := w.walletDelete(ctx, addr); err != nil { + return xerrors.Errorf("wallet delete: %w", err) + } + + if def, err := w.GetDefault(); err == nil { + if def == addr { + w.deleteDefault() + } + } + return nil +} + +func (w *LocalWallet) WalletAddPasswd(ctx context.Context, passwd string, path string) error { + + if key.IsSetup() { + err := xerrors.Errorf("passwd is setup, no need to setup again") + log.Warn(err.Error()) + return err + } + + if err := RegexpPasswd(passwd); err != nil { + return err + } + + err := SetupPasswd([]byte(passwd), path) + if err != nil { + return err + } + + return nil +} + +func (w *LocalWallet) WalletResetPasswd(ctx context.Context, oldPasswd, newPasswd string) (bool, error) { + if !key.IsSetup() { + return false, xerrors.Errorf("passwd is not setup") + } + + if err := CheckPasswd([]byte(oldPasswd)); err != nil { + return false, err + } + + if err := RegexpPasswd(newPasswd); err != nil { + return false, err + } + + addr_list, err := w.WalletList(ctx) + if err != nil { + return false, err + } + + addr_all := make(map[address.Address]*KeyInfo) + for _, v := range addr_list { + k, err := w.findKey(v) + if err != nil { + return false, fmt.Errorf("failed to find key to export: %w", err) + } + if k == nil { + return false, fmt.Errorf("key not found") + } + + pk, err := key.UnMakeByte(k.PrivateKey) + if err != nil { + return false, err + } + + ki := &KeyInfo{ + KeyInfo: types.KeyInfo{ + Type: k.Type, + PrivateKey: pk, + }, + Enc: key.IsPrivateKeyEnc(k.PrivateKey), + } + addr_all[v] = ki + + err = w.WalletDeleteKey2(v) + if err != nil { + return false, err + } + } + + setDefault := true + defalutAddr, err := w.GetDefault() + if err != nil { + setDefault = false + } + + err = ResetPasswd([]byte(newPasswd)) + if err != nil { + return false, err + } + + for addr, v := range addr_all { + k, err := key.NewKey(v.KeyInfo) + if err != nil { + return false, fmt.Errorf("failed to make key: %w", err) + } + + if v.Enc { + pk, err := key.MakeByte(k.PrivateKey) + if err != nil { + return false, err + } + k.PrivateKey = pk + } + + if err := w.keystore.Put(KNamePrefix+k.Address.String(), k.KeyInfo); err != nil { + return false, fmt.Errorf("saving to keystore: %w", err) + } + + if k.Address != addr { + return false, fmt.Errorf("import error") + } + } + + if setDefault { + err = w.SetDefault(defalutAddr) + if err != nil { + return false, err + } + } + + w.WalletClearCache() + + return true, nil +} + +func (w *LocalWallet) WalletClearPasswd(ctx context.Context, passwd string) (bool, error) { + if !key.IsSetup() { + return false, xerrors.Errorf("passwd is not setup") + } + + if err := CheckPasswd([]byte(passwd)); err != nil { + return false, err + } + + addr_list, err := w.WalletList(ctx) + if err != nil { + return false, err + } + addr_all := make(map[address.Address]*types.KeyInfo) + for _, v := range addr_list { + addr_all[v], err = w.WalletExportEncrypt(ctx, v, passwd) + if err != nil { + return false, err + } + err = w.WalletDeleteKey2(v) + if err != nil { + return false, err + } + } + + setDefault := true + defalutAddr, err := w.GetDefault() + if err != nil { + setDefault = false + } + + err = ClearPasswd() + if err != nil { + return false, err + } + + for k, v := range addr_all { + addr, err := w.WalletImport(ctx, v) + if err != nil { + return false, nil + } else if addr != k { + return false, xerrors.Errorf("import error") + } + } + + if setDefault { + err = w.SetDefault(defalutAddr) + if err != nil { + return false, err + } + } + + w.WalletClearCache() + return true, nil +} + +func (w *LocalWallet) WalletCheckPasswd(ctx context.Context, passwd string) bool { + if err := CheckPasswd([]byte(passwd)); err != nil { + return false + } + return true +} + +func (w *LocalWallet) WalletEncrypt(ctx context.Context, addr address.Address) (bool, error) { + if !key.IsSetup() { + return false, xerrors.Errorf("passwd is not setup") + } + + addr_list, err := w.WalletList(ctx) + if err != nil { + return false, err + } + addr_all := make(map[address.Address]*KeyInfo) + for _, v := range addr_list { + if v == addr { + k, err := w.findKey(v) + if err != nil { + return false, fmt.Errorf("failed to find key to export: %w", err) + } + if k == nil { + return false, fmt.Errorf("key not found") + } + + pk, err := key.UnMakeByte(k.PrivateKey) + if err != nil { + return false, err + } + + ki := &KeyInfo{ + KeyInfo: types.KeyInfo{ + Type: k.Type, + PrivateKey: pk, + }, + Enc: key.IsPrivateKeyEnc(k.PrivateKey), + } + addr_all[v] = ki + + err = w.WalletDeleteKey2(v) + if err != nil { + return false, err + } + } + } + + setDefault := true + defalutAddr, err := w.GetDefault() + if err != nil { + setDefault = false + } + + for k, v := range addr_all { + if k == addr { + k, err := key.NewKey(v.KeyInfo) + if err != nil { + return false, fmt.Errorf("failed to make key: %w", err) + } + + pk, err := key.MakeByte(k.PrivateKey) + if err != nil { + return false, err + } + k.PrivateKey = pk + + if err := w.keystore.Put(KNamePrefix+k.Address.String(), k.KeyInfo); err != nil { + return false, fmt.Errorf("saving to keystore: %w", err) + } + + if k.Address != addr { + return false, fmt.Errorf("import error") + } + } + } + + if setDefault { + err = w.SetDefault(defalutAddr) + if err != nil { + return false, err + } + } + + w.WalletClearCache() + return true, nil +} + +func (w *LocalWallet) WalletDecrypt(ctx context.Context, addr address.Address, passwd string) (bool, error) { + if !key.IsSetup() { + return false, xerrors.Errorf("passwd is not setup") + } + + if err := CheckPasswd([]byte(passwd)); err != nil { + return false, err + } + + addr_list, err := w.WalletList(ctx) + if err != nil { + return false, err + } + + addr_all := make(map[address.Address]*KeyInfo) + for _, v := range addr_list { + if v == addr { + k, err := w.findKey(v) + if err != nil { + return false, fmt.Errorf("failed to find key to export: %w", err) + } + if k == nil { + return false, fmt.Errorf("key not found") + } + + pk, err := key.UnMakeByte(k.PrivateKey) + if err != nil { + return false, err + } + + ki := &KeyInfo{ + KeyInfo: types.KeyInfo{ + Type: k.Type, + PrivateKey: pk, + }, + Enc: key.IsPrivateKeyEnc(k.PrivateKey), + } + addr_all[v] = ki + + err = w.WalletDeleteKey2(v) + if err != nil { + return false, err + } + } + } + + setDefault := true + defalutAddr, err := w.GetDefault() + if err != nil { + setDefault = false + } + + for k, v := range addr_all { + if k == addr { + k, err := key.NewKey(v.KeyInfo) + if err != nil { + return false, fmt.Errorf("failed to make key: %w", err) + } + + if err := w.keystore.Put(KNamePrefix+k.Address.String(), k.KeyInfo); err != nil { + return false, fmt.Errorf("saving to keystore: %w", err) + } + + if k.Address != addr { + return false, fmt.Errorf("import error") + } + } + } + + if setDefault { + err = w.SetDefault(defalutAddr) + if err != nil { + return false, err + } + } + + w.WalletClearCache() + return true, nil +} + +func (w *LocalWallet) WalletIsEncrypt(ctx context.Context, addr address.Address) (bool, error) { + if !key.IsSetup() { + log.Infof("passwd is not setup") + return false, nil + } + + addr_list, err := w.WalletList(ctx) + if err != nil { + return false, err + } + + for _, v := range addr_list { + if v == addr { + ki, err := w.tryFind(v) + if err != nil { + if xerrors.Is(err, types.ErrKeyInfoNotFound) { + return false, nil + } + return false, xerrors.Errorf("getting from keystore: %w", err) + } + + // wallet-security 解密 + if bytes.Equal(ki.PrivateKey[:4], key.AddrPrefix) { + return true, nil + } + return false, nil + } + } + return false, nil +} + +func (w *LocalWallet) WalletDeleteKey2(addr address.Address) error { + k, err := w.findKey(addr) + if err != nil { + return xerrors.Errorf("failed to delete key %s : %w", addr, err) + } + + if err := w.keystore.Delete(KNamePrefix + k.Address.String()); err != nil { + return xerrors.Errorf("failed to delete key %s: %w", addr, err) + } + + return nil +} + +func (w *LocalWallet) WalletClearCache() { + w.keys = make(map[address.Address]*key.Key) +} diff --git a/cli/cmd.go b/cli/cmd.go index 802df0c99a..c189e776f9 100644 --- a/cli/cmd.go +++ b/cli/cmd.go @@ -93,3 +93,9 @@ func WithCategory(cat string, cmd *cli.Command) *cli.Command { cmd.Category = strings.ToUpper(cat) return cmd } + +// wallet-security GetWalletCmd +// GetWalletCmd get wallet cmds +func GetWalletCmd() []*cli.Command { + return walletCmd.Subcommands +} diff --git a/cli/send.go b/cli/send.go index cfa2515c07..f6c3794bc4 100644 --- a/cli/send.go +++ b/cli/send.go @@ -17,6 +17,10 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types/ethtypes" + + // wallet-security api + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/wallet" ) var sendCmd = &cli.Command{ @@ -199,6 +203,35 @@ var sendCmd = &cli.Command{ params.Params = decparams } + // wallet-security WalletDefaultAddress + fullapi := srv.FullNodeAPI() + if params.From == address.Undef { + defaddr, err := fullapi.WalletDefaultAddress(ctx) + if err != nil { + return err + } + params.From = defaddr + } + // wallet-security send + if wallet.GetSetupStateForLocal(getWalletPwdRepo(cctx)) { + rest, _ := fullapi.WalletCustomMethod(ctx, api.WalletIsEncrypt, []interface{}{params.From}) + if rest != nil && rest.(bool) { + // passwd := cctx.String("passwd") + passwd := wallet.Prompt("Enter your Password:\n") + if passwd == "" { + return xerrors.Errorf("must enter your passwd") + } + if err := wallet.RegexpPasswd(passwd); err != nil { + return err + } + + rest, _ := fullapi.WalletCustomMethod(ctx, api.WalletCheckPasswd, []interface{}{passwd}) + if !rest.(bool) { + return fmt.Errorf("check passwd is failed") + } + } + } + if cctx.IsSet("nonce") { n := cctx.Uint64("nonce") params.Nonce = &n diff --git a/cli/wallet.go b/cli/wallet.go index 3a21cdaba8..f0ef7b79e7 100644 --- a/cli/wallet.go +++ b/cli/wallet.go @@ -22,6 +22,10 @@ import ( "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/lib/tablewriter" + + // wallet-security api + lapi "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/wallet" ) var walletCmd = &cli.Command{ @@ -39,6 +43,13 @@ var walletCmd = &cli.Command{ walletVerify, walletDelete, walletMarket, + + // wallet-extand mark func cmd + WithCategory("custom", walletMarkCmd), + // wallet-security func cmd + WithCategory("custom", walletPasswdCmd), + WithCategory("custom", walletEncrypt), + WithCategory("custom", walletDecrypt), }, } @@ -102,9 +113,50 @@ var walletList = &cli.Command{ afmt := NewAppFmt(cctx.App) - addrs, err := api.WalletList(ctx) - if err != nil { - return err + // wallet-security walletlist + // addrs, err := api.WalletList(ctx) + // if err != nil { + // return err + // } + encryptAddrs := make([]lapi.AddrListEncrypt, 0) + rest, err := api.WalletCustomMethod(ctx, lapi.WalletListForEnc, []interface{}{}) + if rest != nil { + if err != nil { + return err + } + addrs, _ := json.Marshal(rest) + if err := json.Unmarshal(addrs, &encryptAddrs); err != nil { + return err + } + } else { + addrs, err := api.WalletList(ctx) + if err != nil { + return err + } + for _, v := range addrs { + encryptAddrs = append(encryptAddrs, lapi.AddrListEncrypt{ + Addr: v, + Encrypt: false, + }) + } + } + + // wallet-extand keymark + var keyMark map[string]string + var keymarkPath string = getWalletMarkRepo(cctx) + _, err = os.Stat(keymarkPath) + if err == nil { + sector, err := ioutil.ReadFile(keymarkPath) + if err == nil { + err = json.Unmarshal(sector, &keyMark) + if err != nil { + keyMark = map[string]string{} + } + } else { + keyMark = map[string]string{} + } + } else { + keyMark = map[string]string{} } // Assume an error means no default key is set @@ -118,17 +170,20 @@ var walletList = &cli.Command{ tablewriter.Col("Market(Locked)"), tablewriter.Col("Nonce"), tablewriter.Col("Default"), + tablewriter.Col("Encrypt"), + tablewriter.Col("Mark"), tablewriter.NewLineCol("Error")) - for _, addr := range addrs { + for _, encryptAddr := range encryptAddrs { if cctx.Bool("addr-only") { - afmt.Println(addr.String()) + //afmt.Println(addr.String()) + afmt.Println(encryptAddr.Addr.String()) } else { - a, err := api.StateGetActor(ctx, addr, types.EmptyTSK) + a, err := api.StateGetActor(ctx, encryptAddr.Addr, types.EmptyTSK) if err != nil { if !strings.Contains(err.Error(), "actor not found") { tw.Write(map[string]interface{}{ - "Address": addr, + "Address": encryptAddr.Addr, "Error": err, }) continue @@ -140,16 +195,26 @@ var walletList = &cli.Command{ } row := map[string]interface{}{ - "Address": addr, + "Address": encryptAddr.Addr, "Balance": types.FIL(a.Balance), "Nonce": a.Nonce, + "Encrypt": encryptAddr.Encrypt, } - if addr == def { + if encryptAddr.Addr == def { row["Default"] = "X" } + // wallet-extand keymark + addrmark, exist := keyMark[encryptAddr.Addr.String()] + if !exist && addrmark != "" { + addrmark = "" + } + if addrmark != "" { + row["Mark"] = addrmark + } + if cctx.Bool("id") { - id, err := api.StateLookupID(ctx, addr, types.EmptyTSK) + id, err := api.StateLookupID(ctx, encryptAddr.Addr, types.EmptyTSK) if err != nil { row["ID"] = "n/a" } else { @@ -158,7 +223,7 @@ var walletList = &cli.Command{ } if cctx.Bool("market") { - mbal, err := api.StateMarketBalance(ctx, addr, types.EmptyTSK) + mbal, err := api.StateMarketBalance(ctx, encryptAddr.Addr, types.EmptyTSK) if err == nil { row["Market(Avail)"] = types.FIL(types.BigSub(mbal.Escrow, mbal.Locked)) row["Market(Locked)"] = types.FIL(mbal.Locked) @@ -283,14 +348,49 @@ var walletExport = &cli.Command{ return fmt.Errorf("must specify key to export") } + // wallet-security panic + defer func() { + if err := recover(); err != nil { + fmt.Println("invaild address payload") + } + }() + addr, err := address.NewFromString(cctx.Args().First()) if err != nil { return err } - ki, err := api.WalletExport(ctx, addr) - if err != nil { - return err + // wallet-security walletexport + // ki, err := api.WalletExport(ctx, addr) + // if err != nil { + // return err + // } + ki := new(types.KeyInfo) + rest, _ := api.WalletCustomMethod(ctx, lapi.WalletIsEncrypt, []interface{}{addr}) + if rest != nil && (wallet.GetSetupStateForLocal(getWalletPwdRepo(cctx)) && rest.(bool)) { + // passwd := cctx.String("passwd") + passwd := wallet.Prompt("Enter your Password:\n") + if passwd == "" { + return xerrors.Errorf("must enter your passwd") + } + + if err := wallet.RegexpPasswd(passwd); err != nil { + return err + } + + rest, err := api.WalletCustomMethod(ctx, lapi.WalletExportForEnc, []interface{}{addr, passwd}) + if err != nil { + return err + } + keyinfo, _ := json.Marshal(rest) + if err := json.Unmarshal(keyinfo, ki); err != nil { + return err + } + } else { + ki, err = api.WalletExport(ctx, addr) + if err != nil { + return err + } } b, err := json.Marshal(ki) @@ -513,6 +613,13 @@ var walletDelete = &cli.Command{ return fmt.Errorf("must specify address to delete") } + // wallet-security panic + defer func() { + if err := recover(); err != nil { + fmt.Println("invaild address payload") + } + }() + addr, err := address.NewFromString(cctx.Args().First()) if err != nil { return err @@ -520,7 +627,32 @@ var walletDelete = &cli.Command{ fmt.Println("Soft deleting address:", addr) fmt.Println("Hard deletion of the address in `~/.lotus/keystore` is needed for permanent removal") - return api.WalletDelete(ctx, addr) + + // wallet-security walletdelete + // return api.WalletDelete(ctx, addr) + rest, _ := api.WalletCustomMethod(ctx, lapi.WalletIsEncrypt, []interface{}{addr}) + if rest != nil && (wallet.GetSetupStateForLocal(getWalletPwdRepo(cctx)) && rest.(bool)) { + // passwd := cctx.String("passwd") + passwd := wallet.Prompt("Enter your Password:\n") + if passwd == "" { + return xerrors.Errorf("Must enter your passwd") + } + + if err := wallet.RegexpPasswd(passwd); err != nil { + return err + } + + _, err = api.WalletCustomMethod(ctx, lapi.WalletDeleteForEnc, []interface{}{addr, passwd}) + if err != nil { + return err + } + } else { + if err := api.WalletDelete(ctx, addr); err != nil { + return err + } + } + + return nil }, } diff --git a/cli/wallet_ext.go b/cli/wallet_ext.go new file mode 100644 index 0000000000..a6b1b26484 --- /dev/null +++ b/cli/wallet_ext.go @@ -0,0 +1,529 @@ +package cli + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + + "github.com/mitchellh/go-homedir" + "github.com/urfave/cli/v2" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + + "github.com/filecoin-project/lotus/chain/wallet" + + // wallet-security api + lapi "github.com/filecoin-project/lotus/api" +) + +// wallet-security func cmd +var walletPasswdCmd = &cli.Command{ + Name: "passwd", + Usage: "Manage wallet passwd info", + Subcommands: []*cli.Command{ + walletAddPasswd, + walletResetPasswd, + walletClearPasswd, + }, +} +var walletAddPasswd = &cli.Command{ + Name: "add", + Usage: "Add wallet password", + Action: func(cctx *cli.Context) error { + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + + if wallet.GetSetupStateForLocal(getWalletPwdRepo(cctx)) { + fmt.Println("passwd is setup,no need to setup again") + return nil + } + + // passwd := cctx.String("passwd") + passwd := wallet.Prompt("Enter your password:\n") + if passwd == "" { + return xerrors.Errorf("must enter your passwd") + } + if err := wallet.RegexpPasswd(passwd); err != nil { + return err + } + + passwd_ag := wallet.Prompt("Enter your password again:\n") + if passwd_ag == "" { + return fmt.Errorf("must enter your passwd") + } + if err := wallet.RegexpPasswd(passwd_ag); err != nil { + return err + } + if passwd != passwd_ag { + return fmt.Errorf("the two passwords do not match") + } + + _, err = api.WalletCustomMethod(ctx, lapi.WalletAddPasswd, []interface{}{passwd, getWalletPwdRepo(cctx)}) + if err != nil { + fmt.Println(err.Error()) + return err + } + fmt.Printf("add wallet password success. \n") + return nil + }, +} +var walletResetPasswd = &cli.Command{ + Name: "reset", + Usage: "Reset wallet password", + Action: func(cctx *cli.Context) error { + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + if !wallet.GetSetupStateForLocal(getWalletPwdRepo(cctx)) { + fmt.Println("passwd is not setup") + return nil + } + + // passwd := cctx.String("passwd") + passwd := wallet.Prompt("Enter your old Password:\n") + if passwd == "" { + return xerrors.Errorf("Must enter your old passwd") + } + if err := wallet.RegexpPasswd(passwd); err != nil { + return err + } + + rest, _ := api.WalletCustomMethod(ctx, lapi.WalletCheckPasswd, []interface{}{passwd}) + if !rest.(bool) { + return fmt.Errorf("check passwd is failed") + } + + newPasswd := wallet.Prompt("Enter your new Password:\n") + if passwd == "" { + return xerrors.Errorf("Must enter your new passwd") + } + if err := wallet.RegexpPasswd(newPasswd); err != nil { + return fmt.Errorf("new passwd : %w", err) + } + + newPasswd_ag := wallet.Prompt("Enter your password again:\n") + if newPasswd_ag == "" { + return fmt.Errorf("must enter your passwd") + } + if err := wallet.RegexpPasswd(newPasswd_ag); err != nil { + return err + } + if newPasswd != newPasswd_ag { + return fmt.Errorf("the two passwords do not match") + } + + _, err = api.WalletCustomMethod(ctx, lapi.WalletResetPasswd, []interface{}{passwd, newPasswd}) + if err != nil { + return err + } + + fmt.Printf("wallet passwd change success. \n") + return nil + }, +} +var walletClearPasswd = &cli.Command{ + Name: "clear", + Usage: "Clear wallet password", + Action: func(cctx *cli.Context) error { + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + if !wallet.GetSetupStateForLocal(getWalletPwdRepo(cctx)) { + fmt.Println("passwd is not setup") + return nil + } + + // passwd := cctx.String("passwd") + passwd := wallet.Prompt("Enter your Password:\n") + if passwd == "" { + return xerrors.Errorf("Must enter your passwd") + } + if err := wallet.RegexpPasswd(passwd); err != nil { + return err + } + + _, err = api.WalletCustomMethod(ctx, lapi.WalletClearPasswd, []interface{}{passwd}) + if err != nil { + return err + } + + fmt.Printf("wallet passwd clear success. \n") + return nil + }, +} + +var walletEncrypt = &cli.Command{ + Name: "encrypt", + Usage: "encrypt wallet account", + ArgsUsage: "[address]", + Action: func(cctx *cli.Context) error { + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + if !wallet.GetSetupStateForLocal(getWalletPwdRepo(cctx)) { + fmt.Println("passwd is not setup") + return nil + } + + if cctx.Args().First() == "all" { + // wallet-security encrypt all + encryptAddrs := make([]lapi.AddrListEncrypt, 0) + rest, err := api.WalletCustomMethod(ctx, lapi.WalletListForEnc, []interface{}{}) + if rest != nil { + if err != nil { + return err + } + addrs, _ := json.Marshal(rest) + if err := json.Unmarshal(addrs, &encryptAddrs); err != nil { + return err + } + } else { + l, err := api.WalletList(ctx) + if err != nil { + return err + } + for _, v := range l { + encryptAddrs = append(encryptAddrs, lapi.AddrListEncrypt{ + Addr: v, + Encrypt: false, + }) + } + } + + // passwd := cctx.String("passwd") + passwd := wallet.Prompt("Enter your Password:\n") + if passwd == "" { + return xerrors.Errorf("Must enter your passwd") + } + if err := wallet.RegexpPasswd(passwd); err != nil { + return err + } + + for _, encryptAddr := range encryptAddrs { + if encryptAddr.Encrypt { + continue + } + + rest, _ := api.WalletCustomMethod(ctx, lapi.WalletIsEncrypt, []interface{}{encryptAddr.Addr}) + if rest != nil && !rest.(bool) { + _, err = api.WalletCustomMethod(ctx, lapi.WalletEncrypt, []interface{}{encryptAddr.Addr}) + if err != nil { + return err + } + + fmt.Printf("Wallet %v encrypt success \n", encryptAddr.Addr) + } else { + fmt.Printf("Wallet %v is encrypted \n", encryptAddr.Addr) + } + } + } else { + addr, err := address.NewFromString(cctx.Args().First()) + if err != nil { + return err + } + + rest, _ := api.WalletCustomMethod(ctx, lapi.WalletIsEncrypt, []interface{}{addr}) + if rest != nil && !rest.(bool) { + + _, err = api.WalletCustomMethod(ctx, lapi.WalletEncrypt, []interface{}{addr}) + if err != nil { + return err + } + + fmt.Printf("Wallet encrypt success \n") + } else { + fmt.Printf("Wallet is encrypted \n") + } + } + return nil + }, +} +var walletDecrypt = &cli.Command{ + Name: "decrypt", + Usage: "decrypt wallet account", + ArgsUsage: "[address]", + Action: func(cctx *cli.Context) error { + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + if !wallet.GetSetupStateForLocal(getWalletPwdRepo(cctx)) { + fmt.Println("passwd is not setup") + return nil + } + + if cctx.Args().First() == "all" { + // wallet-security encrypt all + encryptAddrs := make([]lapi.AddrListEncrypt, 0) + rest, err := api.WalletCustomMethod(ctx, lapi.WalletListForEnc, []interface{}{}) + if rest != nil { + if err != nil { + return err + } + addrs, _ := json.Marshal(rest) + if err := json.Unmarshal(addrs, &encryptAddrs); err != nil { + return err + } + } else { + l, err := api.WalletList(ctx) + if err != nil { + return err + } + for _, v := range l { + encryptAddrs = append(encryptAddrs, lapi.AddrListEncrypt{ + Addr: v, + Encrypt: false, + }) + } + } + + // passwd := cctx.String("passwd") + passwd := wallet.Prompt("Enter your Password:\n") + if passwd == "" { + return xerrors.Errorf("Must enter your passwd") + } + if err := wallet.RegexpPasswd(passwd); err != nil { + return err + } + + for _, encryptAddr := range encryptAddrs { + if !encryptAddr.Encrypt { + continue + } + + rest, _ := api.WalletCustomMethod(ctx, lapi.WalletIsEncrypt, []interface{}{encryptAddr.Addr}) + if rest != nil && rest.(bool) { + _, err = api.WalletCustomMethod(ctx, lapi.WalletDecrypt, []interface{}{encryptAddr.Addr, passwd}) + if err != nil { + return err + } + + fmt.Printf("Wallet %v decrypt success \n", encryptAddr.Addr) + } else { + fmt.Printf("Wallet %v is not encrypted \n", encryptAddr.Addr) + } + } + } else { + addr, err := address.NewFromString(cctx.Args().First()) + if err != nil { + return err + } + + rest, _ := api.WalletCustomMethod(ctx, lapi.WalletIsEncrypt, []interface{}{addr}) + if rest != nil && rest.(bool) { + // passwd := cctx.String("passwd") + passwd := wallet.Prompt("Enter your Password:\n") + if passwd == "" { + return xerrors.Errorf("Must enter your passwd") + } + if err := wallet.RegexpPasswd(passwd); err != nil { + return err + } + + _, err = api.WalletCustomMethod(ctx, lapi.WalletDecrypt, []interface{}{addr, passwd}) + if err != nil { + return err + } + + fmt.Printf("Wallet decrypt success \n") + } else { + fmt.Printf("Wallet is not encrypted \n") + } + } + return nil + }, +} + +// wallet-security pwd func +func getWalletPwdRepo(cctx *cli.Context) string { + passwdPath, err := homedir.Expand(cctx.String("repo")) + if err != nil { + return "" + } + return passwdPath + "/keystore/passwd" +} + +// wallet-extand mark func cmd +var walletMarkCmd = &cli.Command{ + Name: "mark", + Usage: "Manage wallet mark info", + Subcommands: []*cli.Command{ + walletAddMarkCmd, + walletDelMarkCmd, + walletClearMarkCmd, + }, +} +var walletAddMarkCmd = &cli.Command{ + Name: "add", + Usage: "Add/Update wallet mark", + ArgsUsage: "[address]", + Action: func(cctx *cli.Context) error { + addr, err := address.NewFromString(cctx.Args().First()) + if err != nil { + return err + } + + mark := wallet.PromptSimple("Enter mark:\n") + if mark == "" { + return xerrors.Errorf("Enter mark is empty.") + } + + var keyMark map[string]string + var keymarkPath string = getWalletMarkRepo(cctx) + _, err = os.Stat(keymarkPath) + if err == nil { + sector, err := ioutil.ReadFile(keymarkPath) + if err == nil { + err = json.Unmarshal(sector, &keyMark) + if err != nil { + keyMark = map[string]string{} + } + } else { + keyMark = map[string]string{} + } + } else { + keyMark = map[string]string{} + } + + keyMark[addr.String()] = mark + + err = saveWalletMarkFile(cctx, keyMark) + if err != nil { + return err + } + + fmt.Printf("wallet add mark success. %v:%v \n", addr.String(), mark) + return nil + }, +} +var walletDelMarkCmd = &cli.Command{ + Name: "del", + Usage: "Delete wallet mark", + ArgsUsage: "[address]", + Action: func(cctx *cli.Context) error { + if !wallet.GetSetupStateForLocal(getWalletMarkRepo(cctx)) { + fmt.Println("mark is not setup") + return nil + } + + addr, err := address.NewFromString(cctx.Args().First()) + if err != nil { + return err + } + + var keyMark map[string]string + var keymarkPath string = getWalletMarkRepo(cctx) + _, err = os.Stat(keymarkPath) + if err == nil { + sector, err := ioutil.ReadFile(keymarkPath) + if err == nil { + err = json.Unmarshal(sector, &keyMark) + if err != nil { + keyMark = map[string]string{} + } + } else { + keyMark = map[string]string{} + } + } else { + keyMark = map[string]string{} + } + + for k := range keyMark { + if k == addr.String() { + delete(keyMark, addr.String()) + } + } + + err = saveWalletMarkFile(cctx, keyMark) + if err != nil { + return err + } + + fmt.Printf("wallet delete mark success. %v \n", addr.String()) + return nil + }, +} +var walletClearMarkCmd = &cli.Command{ + Name: "clear", + Usage: "Clear wallet mark", + Action: func(cctx *cli.Context) error { + if !wallet.GetSetupStateForLocal(getWalletMarkRepo(cctx)) { + fmt.Println("mark is not setup") + return nil + } + + var keyMark map[string]string + var keymarkPath string = getWalletMarkRepo(cctx) + _, err := os.Stat(keymarkPath) + if err == nil { + sector, err := ioutil.ReadFile(keymarkPath) + if err == nil { + err = json.Unmarshal(sector, &keyMark) + if err != nil { + keyMark = map[string]string{} + } + } else { + keyMark = map[string]string{} + } + } else { + keyMark = map[string]string{} + } + + for k := range keyMark { + delete(keyMark, k) + } + + err = saveWalletMarkFile(cctx, keyMark) + if err != nil { + return err + } + + fmt.Printf("wallet clear mark success. \n") + return nil + }, +} + +// wallet-extand mark func +func getWalletMarkRepo(cctx *cli.Context) string { + keymarkPath, err := homedir.Expand(cctx.String("repo")) + if err != nil { + return "" + } + return keymarkPath + "/keystore/keymark" +} + +func saveWalletMarkFile(cctx *cli.Context, keyMark map[string]string) error { + var keymarkPath string = getWalletMarkRepo(cctx) + f, err := os.OpenFile(keymarkPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_SYNC, 0600) + if err != nil { + return err + } + defer f.Close() //nolint:errcheck + + sector, err := json.MarshalIndent(keyMark, "", "\t") + if err != nil { + return err + } + err = ioutil.WriteFile(keymarkPath, sector, 0600) + if err != nil { + return xerrors.Errorf("writing file '%s': %w", keymarkPath, err) + } + return nil +} diff --git a/cmd/lotus-wallet/interactive.go b/cmd/lotus-wallet/interactive.go index 40c3f89223..db8bc0b65f 100644 --- a/cmd/lotus-wallet/interactive.go +++ b/cmd/lotus-wallet/interactive.go @@ -194,6 +194,21 @@ func (c *InteractiveWallet) WalletDelete(ctx context.Context, addr address.Addre return c.under.WalletDelete(ctx, addr) } +// wallet-security InteractiveWallet WalletCustomMethod +func (c *InteractiveWallet) WalletCustomMethod(ctx context.Context, meth api.WalletMethod, args []interface{}) (interface{}, error) { + err := c.accept(func() error { + fmt.Println("-----") + fmt.Println("ACTION: WalletCustomMethod - Wallet extension operation") + fmt.Printf("METHOD: %s\n", meth) + fmt.Printf("Args: %s\n", args) + return nil + }) + if err != nil { + return nil, err + } + return c.under.WalletCustomMethod(ctx, meth, args) +} + func (c *InteractiveWallet) accept(prompt func() error) error { c.lk.Lock() defer c.lk.Unlock() diff --git a/cmd/lotus-wallet/logged.go b/cmd/lotus-wallet/logged.go index 4f07d6ae46..87b0bbd2a1 100644 --- a/cmd/lotus-wallet/logged.go +++ b/cmd/lotus-wallet/logged.go @@ -87,3 +87,9 @@ func (c *LoggedWallet) WalletDelete(ctx context.Context, addr address.Address) e return c.under.WalletDelete(ctx, addr) } + +// wallet-security LoggedWallet WalletCustomMethod +func (c *LoggedWallet) WalletCustomMethod(ctx context.Context, meth api.WalletMethod, args []interface{}) (interface{}, error) { + log.Infof("LoggedWallet WalletCustomMethod meth %+v args %+v ", meth, args) + return c.under.WalletCustomMethod(ctx, meth, args) +} diff --git a/cmd/lotus-wallet/main.go b/cmd/lotus-wallet/main.go index 8360dae15d..b12118c1ce 100644 --- a/cmd/lotus-wallet/main.go +++ b/cmd/lotus-wallet/main.go @@ -78,7 +78,9 @@ To configure your lotus node to use a remote wallet: }, }, - Commands: local, + // wallet-security GetWalletCmd + // Commands: local, + Commands: append(local, lcli.GetWalletCmd()...), } app.Setup() diff --git a/cmd/lotus/daemon.go b/cmd/lotus/daemon.go index 59f9d72588..17a84ba44f 100644 --- a/cmd/lotus/daemon.go +++ b/cmd/lotus/daemon.go @@ -50,6 +50,9 @@ import ( "github.com/filecoin-project/lotus/node/modules/testing" "github.com/filecoin-project/lotus/node/repo" "github.com/filecoin-project/lotus/storage/sealer/ffiwrapper" + + // wallet-security api + "github.com/filecoin-project/lotus/chain/wallet" ) const ( @@ -235,6 +238,18 @@ var DaemonCmd = &cli.Command{ } freshRepo := err != repo.ErrRepoExists + // wallet-security keystore/passwd + passwdPath, err := homedir.Expand(cctx.String("repo")) + if err != nil { + return err + } + ok := wallet.GetSetupState(passwdPath + "/keystore/passwd") + if !ok { + log.Info("Passwd is not setup") + } else { + log.Info("Passwd is setup") + } + if !isLite { if err := paramfetch.GetParams(lcli.ReqContext(cctx), build.ParametersJSON(), build.SrsJSON(), 0); err != nil { return xerrors.Errorf("fetching proof parameters: %w", err) diff --git a/documentation/en/api-v0-methods.md b/documentation/en/api-v0-methods.md index 3e5d0dea4d..35fbb1abbf 100644 --- a/documentation/en/api-v0-methods.md +++ b/documentation/en/api-v0-methods.md @@ -229,6 +229,7 @@ * [SyncValidateTipset](#SyncValidateTipset) * [Wallet](#Wallet) * [WalletBalance](#WalletBalance) + * [WalletCustomMethod](#WalletCustomMethod) * [WalletDefaultAddress](#WalletDefaultAddress) * [WalletDelete](#WalletDelete) * [WalletExport](#WalletExport) @@ -7471,6 +7472,25 @@ Inputs: Response: `"0"` +### WalletCustomMethod +wallet-security FullNodeExt WalletCustomMethod +WalletCustomMethod wallet extension operation + + +Perms: admin + +Inputs: +```json +[ + 0, + [ + {} + ] +] +``` + +Response: `{}` + ### WalletDefaultAddress WalletDefaultAddress returns the address marked as default in the wallet. diff --git a/documentation/en/api-v1-unstable-methods.md b/documentation/en/api-v1-unstable-methods.md index dc98ffae91..f39bcae49a 100644 --- a/documentation/en/api-v1-unstable-methods.md +++ b/documentation/en/api-v1-unstable-methods.md @@ -282,6 +282,7 @@ * [SyncValidateTipset](#SyncValidateTipset) * [Wallet](#Wallet) * [WalletBalance](#WalletBalance) + * [WalletCustomMethod](#WalletCustomMethod) * [WalletDefaultAddress](#WalletDefaultAddress) * [WalletDelete](#WalletDelete) * [WalletExport](#WalletExport) @@ -8727,6 +8728,25 @@ Inputs: Response: `"0"` +### WalletCustomMethod +wallet-security FullNodeExt WalletCustomMethod +WalletCustomMethod wallet extension operation + + +Perms: admin + +Inputs: +```json +[ + 0, + [ + {} + ] +] +``` + +Response: `{}` + ### WalletDefaultAddress WalletDefaultAddress returns the address marked as default in the wallet.