diff --git a/client/client.go b/client/client.go index 303e217..a54d6d9 100644 --- a/client/client.go +++ b/client/client.go @@ -4,14 +4,18 @@ import ( "context" "crypto/ed25519" "encoding/json" + "errors" "fmt" "log" "path/filepath" "sync" "time" + "github.com/depools/dc4bc/fsm/state_machines/signature_proposal_fsm" + + "github.com/depools/dc4bc/fsm/state_machines" + "github.com/depools/dc4bc/fsm/fsm" - fsmStateMachines "github.com/depools/dc4bc/fsm/state_machines" dkgFSM "github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm" "github.com/depools/dc4bc/qr" "github.com/depools/dc4bc/storage" @@ -25,8 +29,8 @@ const ( type Client struct { sync.Mutex userName string + address string ctx context.Context - fsm *fsmStateMachines.FSMInstance state State storage storage.Storage keyStore KeyStore @@ -36,16 +40,20 @@ type Client struct { func NewClient( ctx context.Context, userName string, - fsm *fsmStateMachines.FSMInstance, state State, storage storage.Storage, keyStore KeyStore, qrProcessor qr.Processor, ) (*Client, error) { + keyPair, err := keyStore.LoadKeys(userName, "") + if err != nil { + return nil, fmt.Errorf("failed to LoadKeys: %w", err) + } + return &Client{ ctx: ctx, userName: userName, - fsm: fsm, + address: keyPair.GetAddr(), state: state, storage: storage, keyStore: keyStore, @@ -89,12 +97,23 @@ func (c *Client) Poll() error { } func (c *Client) ProcessMessage(message storage.Message) error { + fsmInstance, err := c.getFSMInstance(message.DkgRoundID) + if err != nil { + return fmt.Errorf("failed to getFSMInstance: %w", err) + } + + if fsm.Event(message.Event) != signature_proposal_fsm.EventInitProposal { + if err := c.verifyMessage(fsmInstance, message); err != nil { + return fmt.Errorf("failed to verifyMessage %+v: %w", message, err) + } + } + fsmReq, err := FSMRequestFromMessage(message) if err != nil { return fmt.Errorf("failed to get FSMRequestFromMessage: %v", err) } - resp, fsmDump, err := c.fsm.Do(fsm.Event(message.Event), fsmReq) + resp, fsmDump, err := fsmInstance.Do(fsm.Event(message.Event), fsmReq) if err != nil { return fmt.Errorf("failed to Do operation in FSM: %w", err) } @@ -129,7 +148,7 @@ func (c *Client) ProcessMessage(message storage.Message) error { return fmt.Errorf("failed to SaveOffset: %w", err) } - if err := c.state.SaveFSM(fsmDump); err != nil { + if err := c.state.SaveFSM(message.DkgRoundID, fsmDump); err != nil { return fmt.Errorf("failed to SaveFSM: %w", err) } @@ -198,9 +217,8 @@ func (c *Client) handleProcessedOperation(operation Operation) error { } message := storage.Message{ - Sender: c.userName, - Event: string(operation.Type), - Data: operation.Result, + Event: string(operation.Type), + Data: operation.Result, } sig, err := c.signMessage(message.Bytes()) @@ -220,6 +238,31 @@ func (c *Client) handleProcessedOperation(operation Operation) error { return nil } +func (c *Client) getFSMInstance(dkgRoundID string) (*state_machines.FSMInstance, error) { + var err error + fsmInstance, ok, err := c.state.LoadFSM(dkgRoundID) + if err != nil { + return nil, fmt.Errorf("failed to LoadFSM: %w", err) + } + + if !ok { + fsmInstance, err = state_machines.Create(dkgRoundID) + if err != nil { + return nil, fmt.Errorf("failed to create FSM instance: %w", err) + } + + bz, err := fsmInstance.Dump() + if err != nil { + return nil, fmt.Errorf("failed to Dump FSM instance: %w", err) + } + if err := c.state.SaveFSM(dkgRoundID, bz); err != nil { + return nil, fmt.Errorf("failed to SaveFSM: %w", err) + } + } + + return fsmInstance, nil +} + func (c *Client) signMessage(message []byte) ([]byte, error) { keyPair, err := c.keyStore.LoadKeys(c.userName, "") if err != nil { @@ -228,3 +271,16 @@ func (c *Client) signMessage(message []byte) ([]byte, error) { return ed25519.Sign(keyPair.Priv, message), nil } + +func (c *Client) verifyMessage(fsmInstance *state_machines.FSMInstance, message storage.Message) error { + senderPubKey, err := fsmInstance.GetPubKeyByAddr(message.SenderAddr) + if err != nil { + return fmt.Errorf("failed to GetPubKeyByAddr: %w", err) + } + + if !ed25519.Verify(senderPubKey, message.Bytes(), message.Signature) { + return errors.New("signature is corrupt") + } + + return nil +} diff --git a/client/client_test.go b/client/client_test.go index 22f5ac9..487db28 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -31,17 +31,21 @@ func TestClient_ProcessMessage(t *testing.T) { ) defer ctrl.Finish() + dkgRoundID := "dkg_round_id" state := clientMocks.NewMockState(ctrl) keyStore := clientMocks.NewMockKeyStore(ctrl) stg := storageMocks.NewMockStorage(ctrl) qrProcessor := qrMocks.NewMockProcessor(ctrl) - fsm, err := state_machines.Create() + testClientKeyPair := client.NewKeyPair() + keyStore.EXPECT().LoadKeys("test_client", "").Times(1).Return(testClientKeyPair, nil) + + fsm, err := state_machines.Create(dkgRoundID) + state.EXPECT().LoadFSM(dkgRoundID).Times(1).Return(fsm, true, nil) clt, err := client.NewClient( ctx, "test_client", - fsm, state, stg, keyStore, @@ -55,22 +59,22 @@ func TestClient_ProcessMessage(t *testing.T) { messageData := requests.SignatureProposalParticipantsListRequest{ Participants: []*requests.SignatureProposalParticipantsEntry{ { - Title: senderUserName, + Addr: senderUserName, PubKey: senderKeyPair.Pub, DkgPubKey: make([]byte, 128), }, { - Title: "111", + Addr: "111", PubKey: client.NewKeyPair().Pub, DkgPubKey: make([]byte, 128), }, { - Title: "222", + Addr: "222", PubKey: client.NewKeyPair().Pub, DkgPubKey: make([]byte, 128), }, { - Title: "333", + Addr: "333", PubKey: client.NewKeyPair().Pub, DkgPubKey: make([]byte, 128), }, @@ -81,16 +85,17 @@ func TestClient_ProcessMessage(t *testing.T) { req.NoError(err) message := storage.Message{ - ID: uuid.New().String(), - Offset: 1, - Event: string(spf.EventInitProposal), - Data: messageDataBz, - Sender: senderUserName, + ID: uuid.New().String(), + DkgRoundID: dkgRoundID, + Offset: 1, + Event: string(spf.EventInitProposal), + Data: messageDataBz, + SenderAddr: senderUserName, } message.Signature = ed25519.Sign(senderKeyPair.Priv, message.Bytes()) state.EXPECT().SaveOffset(uint64(1)).Times(1).Return(nil) - state.EXPECT().SaveFSM(gomock.Any()).Times(1).Return(nil) + state.EXPECT().SaveFSM(gomock.Any(), gomock.Any()).Times(1).Return(nil) err = clt.ProcessMessage(message) req.NoError(err) @@ -113,7 +118,6 @@ func TestClient_GetOperationsList(t *testing.T) { clt, err := client.NewClient( ctx, "test_client", - nil, state, stg, keyStore, @@ -156,7 +160,6 @@ func TestClient_GetOperationQRPath(t *testing.T) { clt, err := client.NewClient( ctx, "test_client", - nil, state, stg, keyStore, @@ -203,7 +206,6 @@ func TestClient_ReadProcessedOperation(t *testing.T) { clt, err := client.NewClient( ctx, "test_client", - nil, state, stg, keyStore, diff --git a/client/keystore.go b/client/keystore.go index cb7f8f4..4f25a31 100644 --- a/client/keystore.go +++ b/client/keystore.go @@ -2,6 +2,7 @@ package client import ( "crypto/ed25519" + "encoding/hex" "encoding/json" "fmt" @@ -90,3 +91,7 @@ func NewKeyPair() *KeyPair { Priv: priv, } } + +func (p *KeyPair) GetAddr() string { + return hex.EncodeToString(p.Pub) +} diff --git a/client/state.go b/client/state.go index e9edcaf..7707b4e 100644 --- a/client/state.go +++ b/client/state.go @@ -7,6 +7,8 @@ import ( "fmt" "sync" + "github.com/depools/dc4bc/fsm/state_machines" + "github.com/syndtr/goleveldb/leveldb" ) @@ -20,8 +22,8 @@ type State interface { SaveOffset(uint64) error LoadOffset() (uint64, error) - SaveFSM([]byte) error - LoadFSM() ([]byte, error) + SaveFSM(dkgRoundID string, dump []byte) error + LoadFSM(dkgRoundID string) (*state_machines.FSMInstance, bool, error) PutOperation(operation *Operation) error DeleteOperation(operationID string) error @@ -105,17 +107,48 @@ func (s *LevelDBState) LoadOffset() (uint64, error) { return offset, nil } -// TODO: implement. -func (s *LevelDBState) SaveFSM(fsmState []byte) error { - if err := s.stateDb.Put([]byte(fsmStateKey), fsmState, nil); err != nil { +func (s *LevelDBState) SaveFSM(dkgRoundID string, dump []byte) error { + bz, err := s.stateDb.Get([]byte(fsmStateKey), nil) + if err != nil { + return fmt.Errorf("failed to get FSM instances: %w", err) + } + + var fsmInstances = map[string][]byte{} + if err := json.Unmarshal(bz, &fsmInstances); err != nil { + return fmt.Errorf("failed to unmarshal FSM instances: %w", err) + } + + fsmInstances[dkgRoundID] = dump + + if err := s.stateDb.Put([]byte(fsmStateKey), dump, nil); err != nil { return fmt.Errorf("failed to save fsm state: %w", err) } + return nil } -// TODO: implement. -func (s *LevelDBState) LoadFSM() ([]byte, error) { - return s.stateDb.Get([]byte(fsmStateKey), nil) +func (s *LevelDBState) LoadFSM(dkgRoundID string) (*state_machines.FSMInstance, bool, error) { + bz, err := s.stateDb.Get([]byte(fsmStateKey), nil) + if err != nil { + return nil, false, fmt.Errorf("failed to get FSM instances: %w", err) + } + + var fsmInstances = map[string][]byte{} + if err := json.Unmarshal(bz, &fsmInstances); err != nil { + return nil, false, fmt.Errorf("failed to unmarshal FSM instances: %w", err) + } + + fsmInstanceBz, ok := fsmInstances[dkgRoundID] + if !ok { + return nil, false, nil + } + + fsmInstance, err := state_machines.FromDump(fsmInstanceBz) + if err != nil { + return nil, false, fmt.Errorf("failed to restore FSM instance from dump: %w", err) + } + + return fsmInstance, ok, nil } func (s *LevelDBState) PutOperation(operation *Operation) error { diff --git a/fsm/state_machines/provider.go b/fsm/state_machines/provider.go index 56f7bb4..e948717 100644 --- a/fsm/state_machines/provider.go +++ b/fsm/state_machines/provider.go @@ -1,13 +1,15 @@ package state_machines import ( + "crypto/ed25519" "crypto/rand" "encoding/base64" "encoding/json" "errors" - "github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm" "strings" + "github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm" + "github.com/depools/dc4bc/fsm/fsm" "github.com/depools/dc4bc/fsm/fsm_pool" "github.com/depools/dc4bc/fsm/state_machines/internal" @@ -43,19 +45,13 @@ func init() { // Create new fsm with unique id // transactionId required for unique identify dump -func Create() (*FSMInstance, error) { +func Create(dkgID string) (*FSMInstance, error) { var ( err error i = &FSMInstance{} ) - transactionId, err := generateDkgTransactionId() - - if err != nil { - return nil, err - } - - err = i.InitDump(transactionId) + err = i.InitDump(dkgID) if err != nil { return nil, err } @@ -90,6 +86,10 @@ func FromDump(data []byte) (*FSMInstance, error) { return i, err } +func (i *FSMInstance) GetPubKeyByAddr(addr string) (ed25519.PublicKey, error) { + return ed25519.PublicKey{}, nil +} + func (i *FSMInstance) Do(event fsm.Event, args ...interface{}) (result *fsm.Response, dump []byte, err error) { var dumpErr error diff --git a/fsm/state_machines/provider_test.go b/fsm/state_machines/provider_test.go index 06d121b..b0b2a2d 100644 --- a/fsm/state_machines/provider_test.go +++ b/fsm/state_machines/provider_test.go @@ -7,14 +7,15 @@ import ( "crypto/x509" "encoding/base64" "fmt" + "log" + "testing" + "time" + "github.com/depools/dc4bc/fsm/fsm" dpf "github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm" spf "github.com/depools/dc4bc/fsm/state_machines/signature_proposal_fsm" "github.com/depools/dc4bc/fsm/types/requests" "github.com/depools/dc4bc/fsm/types/responses" - "log" - "testing" - "time" ) type testExternalParticipants struct { @@ -74,7 +75,7 @@ func init() { for _, participant := range testParticipants { participantsForRequest = append(participantsForRequest, &requests.SignatureProposalParticipantsEntry{ - Title: participant.Title, + Addr: participant.Title, PubKey: x509.MarshalPKCS1PublicKey(participant.PubKey), DkgPubKey: participant.DkgPubKey, }) @@ -186,7 +187,7 @@ func Test_SignatureProposal_Positive(t *testing.T) { } if participant.Title == "" { - t.Fatalf("expected not empty {Title}") + t.Fatalf("expected not empty {Addr}") } if participant.EncryptedInvitation == "" { diff --git a/fsm/state_machines/signature_proposal_fsm/actions.go b/fsm/state_machines/signature_proposal_fsm/actions.go index 45a27ed..cebc4aa 100644 --- a/fsm/state_machines/signature_proposal_fsm/actions.go +++ b/fsm/state_machines/signature_proposal_fsm/actions.go @@ -4,12 +4,13 @@ import ( "crypto/x509" "errors" "fmt" + "time" + "github.com/depools/dc4bc/fsm/config" "github.com/depools/dc4bc/fsm/fsm" "github.com/depools/dc4bc/fsm/state_machines/internal" "github.com/depools/dc4bc/fsm/types/requests" "github.com/depools/dc4bc/fsm/types/responses" - "time" ) // init -> awaitingConfirmations @@ -53,7 +54,7 @@ func (m *SignatureProposalFSM) actionInitSignatureProposal(inEvent fsm.Event, ar m.payload.SignatureProposalPayload.Quorum[participantId] = &internal.SignatureProposalParticipant{ ParticipantId: index, - Title: participant.Title, + Title: participant.Addr, PubKey: parsedPubKey, DkgPubKey: participant.DkgPubKey, InvitationSecret: secret, diff --git a/fsm/state_machines/signature_proposal_fsm/helpers.go b/fsm/state_machines/signature_proposal_fsm/helpers.go index a8cfaa0..69a7559 100644 --- a/fsm/state_machines/signature_proposal_fsm/helpers.go +++ b/fsm/state_machines/signature_proposal_fsm/helpers.go @@ -5,6 +5,7 @@ import ( "crypto/rsa" "crypto/sha1" "encoding/base64" + "github.com/depools/dc4bc/fsm/state_machines/internal" "github.com/depools/dc4bc/fsm/types/responses" ) @@ -13,12 +14,12 @@ import ( func ProposalParticipantsQuorumToResponse(list *internal.SignatureProposalQuorum) responses.SignatureProposalParticipantInvitationsResponse { var response responses.SignatureProposalParticipantInvitationsResponse - for quorumId, parcipant := range *list { + for quorumId, participant := range *list { response = append(response, &responses.SignatureProposalParticipantInvitationEntry{ - Title: parcipant.Title, + Title: participant.Title, PubKeyFingerprint: quorumId, // TODO: Add encryption - EncryptedInvitation: parcipant.InvitationSecret, + EncryptedInvitation: participant.InvitationSecret, }) } return response diff --git a/fsm/types/requests/signature_proposal.go b/fsm/types/requests/signature_proposal.go index 8de5b8d..51b60fc 100644 --- a/fsm/types/requests/signature_proposal.go +++ b/fsm/types/requests/signature_proposal.go @@ -13,7 +13,7 @@ type SignatureProposalParticipantsListRequest struct { type SignatureProposalParticipantsEntry struct { // Public title for address, such as name, nickname, organization - Title string + Addr string PubKey []byte DkgPubKey []byte } diff --git a/fsm/types/requests/signature_proposal_validation.go b/fsm/types/requests/signature_proposal_validation.go index accbb62..3bb1e07 100644 --- a/fsm/types/requests/signature_proposal_validation.go +++ b/fsm/types/requests/signature_proposal_validation.go @@ -3,6 +3,7 @@ package requests import ( "errors" "fmt" + "github.com/depools/dc4bc/fsm/config" ) @@ -12,12 +13,12 @@ func (r *SignatureProposalParticipantsListRequest) Validate() error { } for _, participant := range r.Participants { - if len(participant.Title) < 3 { - return errors.New("{Title} minimum length is {3}") + if len(participant.Addr) < 3 { + return errors.New("{Addr} minimum length is {3}") } - if len(participant.Title) > 150 { - return errors.New("{Title} maximum length is {150}") + if len(participant.Addr) > 150 { + return errors.New("{Addr} maximum length is {150}") } if len(participant.PubKey) < 10 { diff --git a/mocks/clientMocks/state_mock.go b/mocks/clientMocks/state_mock.go index fbef32a..a0090eb 100644 --- a/mocks/clientMocks/state_mock.go +++ b/mocks/clientMocks/state_mock.go @@ -6,6 +6,7 @@ package clientMocks import ( client "github.com/depools/dc4bc/client" + state_machines "github.com/depools/dc4bc/fsm/state_machines" gomock "github.com/golang/mock/gomock" reflect "reflect" ) @@ -63,32 +64,33 @@ func (mr *MockStateMockRecorder) LoadOffset() *gomock.Call { } // SaveFSM mocks base method -func (m *MockState) SaveFSM(arg0 []byte) error { +func (m *MockState) SaveFSM(dkgRoundID string, dump []byte) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SaveFSM", arg0) + ret := m.ctrl.Call(m, "SaveFSM", dkgRoundID, dump) ret0, _ := ret[0].(error) return ret0 } // SaveFSM indicates an expected call of SaveFSM -func (mr *MockStateMockRecorder) SaveFSM(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) SaveFSM(dkgRoundID, dump interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveFSM", reflect.TypeOf((*MockState)(nil).SaveFSM), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveFSM", reflect.TypeOf((*MockState)(nil).SaveFSM), dkgRoundID, dump) } // LoadFSM mocks base method -func (m *MockState) LoadFSM() ([]byte, error) { +func (m *MockState) LoadFSM(dkgRoundID string) (*state_machines.FSMInstance, bool, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "LoadFSM") - ret0, _ := ret[0].([]byte) - ret1, _ := ret[1].(error) - return ret0, ret1 + ret := m.ctrl.Call(m, "LoadFSM", dkgRoundID) + ret0, _ := ret[0].(*state_machines.FSMInstance) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 } // LoadFSM indicates an expected call of LoadFSM -func (mr *MockStateMockRecorder) LoadFSM() *gomock.Call { +func (mr *MockStateMockRecorder) LoadFSM(dkgRoundID interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadFSM", reflect.TypeOf((*MockState)(nil).LoadFSM)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadFSM", reflect.TypeOf((*MockState)(nil).LoadFSM), dkgRoundID) } // PutOperation mocks base method diff --git a/storage/types.go b/storage/types.go index ddc6e64..0107382 100644 --- a/storage/types.go +++ b/storage/types.go @@ -6,19 +6,17 @@ import ( ) type Message struct { - ID string `json:"id"` - Offset uint64 `json:"offset"` - Event string `json:"event"` - Data []byte `json:"data"` - Signature []byte `json:"signature"` - Sender string `json:"sender"` + ID string `json:"id"` + DkgRoundID string `json:"dkg_round_id"` + Offset uint64 `json:"offset"` + Event string `json:"event"` + Data []byte `json:"data"` + Signature []byte `json:"signature"` + SenderAddr string `json:"sender"` } func (m *Message) Bytes() []byte { buf := bytes.NewBuffer(nil) - - buf.Write([]byte(m.Sender)) - buf.Write([]byte(m.Event)) buf.Write(m.Data) return buf.Bytes()