From db8763ffffdced7ce93ead17a540f8d7de595f6b Mon Sep 17 00:00:00 2001 From: Andrej Zavgorodnij Date: Fri, 14 Aug 2020 15:34:15 +0300 Subject: [PATCH 1/6] feat: hot signatures --- client/client.go | 144 +++++++++++++++-------------- client/client_test.go | 129 ++++++++++++++++++++++++-- client/keystore.go | 92 ++++++++++++++++++ client/state.go | 22 +++-- client/types.go | 57 +++++++++--- cmd/client/main.go | 23 ++++- main.go | 8 ++ mocks/clientMocks/state_mock.go | 5 +- mocks/gomock.go | 1 + mocks/storageMocks/storage_mock.go | 3 +- storage/types.go | 25 ++++- 11 files changed, 399 insertions(+), 110 deletions(-) create mode 100644 client/keystore.go diff --git a/client/client.go b/client/client.go index 0001d14..303e217 100644 --- a/client/client.go +++ b/client/client.go @@ -2,20 +2,19 @@ package client import ( "context" + "crypto/ed25519" "encoding/json" "fmt" - dkgFSM "github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm" - "go.dedis.ch/kyber/v3" "log" "path/filepath" "sync" "time" + "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" - sign "go.dedis.ch/kyber/v3/sign/schnorr" - "go.dedis.ch/kyber/v3/util/key" ) const ( @@ -25,29 +24,31 @@ const ( type Client struct { sync.Mutex + userName string ctx context.Context fsm *fsmStateMachines.FSMInstance state State storage storage.Storage + keyStore KeyStore qrProcessor qr.Processor - - // these just a template - suite key.Suite - authKeyPair *key.Pair } func NewClient( ctx context.Context, + userName string, fsm *fsmStateMachines.FSMInstance, state State, storage storage.Storage, + keyStore KeyStore, qrProcessor qr.Processor, ) (*Client, error) { return &Client{ ctx: ctx, + userName: userName, fsm: fsm, state: state, storage: storage, + keyStore: keyStore, qrProcessor: qrProcessor, }, nil } @@ -76,48 +77,8 @@ func (c *Client) Poll() error { } for _, message := range messages { - log.Println("Message:", message) - - fsmReq, err := FSMRequestFromBytes(message.Data) - if err != nil { - return fmt.Errorf("failed to get FSMRequest from message data: %w", err) - } - - resp, fsmDump, err := c.fsm.Do(fsmReq.Event, fsmReq.Args...) - if err != nil { - return fmt.Errorf("failed to Do operation in FSM: %w", err) - } - - var operation *Operation - - switch resp.State { - // if the new state is waiting for RPC to airgapped machine - case dkgFSM.StateDkgPubKeysAwaitConfirmations, dkgFSM.StateDkgCommitsAwaitConfirmations, - dkgFSM.StateDkgDealsAwaitConfirmations, dkgFSM.StateDkgResponsesAwaitConfirmations: - bz, err := json.Marshal(resp.Data) - if err != nil { - return fmt.Errorf("failed to marshal FSM response: %w", err) - } - operation = &Operation{ - Type: OperationType(resp.State), - Payload: bz, - } - default: - log.Printf("State %s does not require an operation", resp.State) - } - - if operation != nil { - if err := c.state.PutOperation(operation); err != nil { - return fmt.Errorf("failed to PutOperation: %w", err) - } - } - - if err := c.state.SaveOffset(message.Offset); err != nil { - return fmt.Errorf("failed to SaveOffset: %w", err) - } - - if err := c.state.SaveFSM(fsmDump); err != nil { - return fmt.Errorf("failed to SaveFSM: %w", err) + if err := c.ProcessMessage(message); err != nil { + log.Println("Failed to process message:", err) } } case <-c.ctx.Done(): @@ -127,6 +88,54 @@ func (c *Client) Poll() error { } } +func (c *Client) ProcessMessage(message storage.Message) error { + 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) + if err != nil { + return fmt.Errorf("failed to Do operation in FSM: %w", err) + } + + var operation *Operation + switch resp.State { + // if the new state is waiting for RPC to airgapped machine + case + dkgFSM.StateDkgCommitsAwaitConfirmations, + dkgFSM.StateDkgDealsAwaitConfirmations, + dkgFSM.StateDkgResponsesAwaitConfirmations: + bz, err := json.Marshal(resp.Data) + if err != nil { + return fmt.Errorf("failed to marshal FSM response: %w", err) + } + + operation = &Operation{ + Type: OperationType(resp.State), + Payload: bz, + } + default: + log.Printf("State %s does not require an operation", resp.State) + } + + if operation != nil { + if err := c.state.PutOperation(operation); err != nil { + return fmt.Errorf("failed to PutOperation: %w", err) + } + } + + if err := c.state.SaveOffset(message.Offset); err != nil { + return fmt.Errorf("failed to SaveOffset: %w", err) + } + + if err := c.state.SaveFSM(fsmDump); err != nil { + return fmt.Errorf("failed to SaveFSM: %w", err) + } + + return nil +} + func (c *Client) GetOperations() (map[string]*Operation, error) { return c.state.GetOperations() } @@ -188,14 +197,17 @@ func (c *Client) handleProcessedOperation(operation Operation) error { return fmt.Errorf("processed operation does not match stored operation: %w", err) } - sig, err := c.signMessage(operation.Result) + message := storage.Message{ + Sender: c.userName, + Event: string(operation.Type), + Data: operation.Result, + } + + sig, err := c.signMessage(message.Bytes()) if err != nil { return fmt.Errorf("failed to sign a message: %w", err) } - message := storage.Message{ - Data: operation.Result, - Signature: sig, - } + message.Signature = sig if _, err := c.storage.Send(message); err != nil { return fmt.Errorf("failed to post message: %w", err) @@ -208,21 +220,11 @@ func (c *Client) handleProcessedOperation(operation Operation) error { return nil } -// it's just a template -func (c *Client) signMessage(msg []byte) ([]byte, error) { - //s, err := sign.Sign(c.suite, c.authKeyPair.Private, msg) - //if err != nil { - // return nil, fmt.Errorf("failed to sign a message: %w", err) - //} - return nil, nil -} +func (c *Client) signMessage(message []byte) ([]byte, error) { + keyPair, err := c.keyStore.LoadKeys(c.userName, "") + if err != nil { + return nil, fmt.Errorf("failed to LoadKeys: %w", err) + } -// it's just a template -func (c *Client) verifyMessage(participant string, msg, signature []byte) error { - return sign.Verify(c.suite, c.getPublicKeyOfParticipant(participant), msg, signature) -} - -// func should return public key of participant for checking his message signature -func (c *Client) getPublicKeyOfParticipant(participant string) kyber.Point { - return nil + return ed25519.Sign(keyPair.Priv, message), nil } diff --git a/client/client_test.go b/client/client_test.go index d539894..22f5ac9 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -2,6 +2,7 @@ package client_test import ( "context" + "crypto/ed25519" "encoding/json" "errors" "os" @@ -9,16 +10,93 @@ import ( "testing" "time" - "github.com/depools/dc4bc/mocks/qrMocks" - "github.com/depools/dc4bc/client" - + "github.com/depools/dc4bc/fsm/state_machines" + spf "github.com/depools/dc4bc/fsm/state_machines/signature_proposal_fsm" + "github.com/depools/dc4bc/fsm/types/requests" "github.com/depools/dc4bc/mocks/clientMocks" + "github.com/depools/dc4bc/mocks/qrMocks" "github.com/depools/dc4bc/mocks/storageMocks" + "github.com/depools/dc4bc/storage" "github.com/golang/mock/gomock" + "github.com/google/uuid" "github.com/stretchr/testify/require" ) +func TestClient_ProcessMessage(t *testing.T) { + var ( + ctx = context.Background() + req = require.New(t) + ctrl = gomock.NewController(t) + ) + defer ctrl.Finish() + + state := clientMocks.NewMockState(ctrl) + keyStore := clientMocks.NewMockKeyStore(ctrl) + stg := storageMocks.NewMockStorage(ctrl) + qrProcessor := qrMocks.NewMockProcessor(ctrl) + + fsm, err := state_machines.Create() + + clt, err := client.NewClient( + ctx, + "test_client", + fsm, + state, + stg, + keyStore, + qrProcessor, + ) + req.NoError(err) + + t.Run("test_process_init_dkg", func(t *testing.T) { + senderUserName := "sender_username" + senderKeyPair := client.NewKeyPair() + messageData := requests.SignatureProposalParticipantsListRequest{ + Participants: []*requests.SignatureProposalParticipantsEntry{ + { + Title: senderUserName, + PubKey: senderKeyPair.Pub, + DkgPubKey: make([]byte, 128), + }, + { + Title: "111", + PubKey: client.NewKeyPair().Pub, + DkgPubKey: make([]byte, 128), + }, + { + Title: "222", + PubKey: client.NewKeyPair().Pub, + DkgPubKey: make([]byte, 128), + }, + { + Title: "333", + PubKey: client.NewKeyPair().Pub, + DkgPubKey: make([]byte, 128), + }, + }, + CreatedAt: time.Now(), + } + messageDataBz, err := json.Marshal(messageData) + req.NoError(err) + + message := storage.Message{ + ID: uuid.New().String(), + Offset: 1, + Event: string(spf.EventInitProposal), + Data: messageDataBz, + Sender: 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) + + err = clt.ProcessMessage(message) + req.NoError(err) + }) +} + func TestClient_GetOperationsList(t *testing.T) { var ( ctx = context.Background() @@ -28,10 +106,19 @@ func TestClient_GetOperationsList(t *testing.T) { defer ctrl.Finish() state := clientMocks.NewMockState(ctrl) - storage := storageMocks.NewMockStorage(ctrl) + keyStore := clientMocks.NewMockKeyStore(ctrl) + stg := storageMocks.NewMockStorage(ctrl) qrProcessor := qrMocks.NewMockProcessor(ctrl) - clt, err := client.NewClient(ctx, nil, state, storage, qrProcessor) + clt, err := client.NewClient( + ctx, + "test_client", + nil, + state, + stg, + keyStore, + qrProcessor, + ) req.NoError(err) state.EXPECT().GetOperations().Times(1).Return(map[string]*client.Operation{}, nil) @@ -62,10 +149,19 @@ func TestClient_GetOperationQRPath(t *testing.T) { defer ctrl.Finish() state := clientMocks.NewMockState(ctrl) - storage := storageMocks.NewMockStorage(ctrl) + keyStore := clientMocks.NewMockKeyStore(ctrl) + stg := storageMocks.NewMockStorage(ctrl) qrProcessor := qrMocks.NewMockProcessor(ctrl) - clt, err := client.NewClient(ctx, nil, state, storage, qrProcessor) + clt, err := client.NewClient( + ctx, + "test_client", + nil, + state, + stg, + keyStore, + qrProcessor, + ) req.NoError(err) operation := &client.Operation{ @@ -100,10 +196,19 @@ func TestClient_ReadProcessedOperation(t *testing.T) { defer ctrl.Finish() state := clientMocks.NewMockState(ctrl) - storage := storageMocks.NewMockStorage(ctrl) + keyStore := clientMocks.NewMockKeyStore(ctrl) + stg := storageMocks.NewMockStorage(ctrl) qrProcessor := qrMocks.NewMockProcessor(ctrl) - clt, err := client.NewClient(ctx, nil, state, storage, qrProcessor) + clt, err := client.NewClient( + ctx, + "test_client", + nil, + state, + stg, + keyStore, + qrProcessor, + ) req.NoError(err) operation := &client.Operation{ @@ -126,7 +231,11 @@ func TestClient_ReadProcessedOperation(t *testing.T) { qrProcessor.EXPECT().ReadQR().Return(processedOperationBz, nil).Times(1) state.EXPECT().GetOperationByID(processedOperation.ID).Times(1).Return(operation, nil) state.EXPECT().DeleteOperation(processedOperation.ID).Times(1) - storage.EXPECT().Send(gomock.Any()).Times(1) + stg.EXPECT().Send(gomock.Any()).Times(1) + + keyPair := client.NewKeyPair() + keyStore.EXPECT().LoadKeys("test_client", "").Times(1).Return(keyPair, nil) + err = clt.ReadProcessedOperation() req.NoError(err) } diff --git a/client/keystore.go b/client/keystore.go new file mode 100644 index 0000000..cb7f8f4 --- /dev/null +++ b/client/keystore.go @@ -0,0 +1,92 @@ +package client + +import ( + "crypto/ed25519" + "encoding/json" + "fmt" + + "github.com/syndtr/goleveldb/leveldb" +) + +const ( + secretsKey = "secrets" +) + +type KeyStore interface { + LoadKeys(userName, password string) (*KeyPair, error) +} + +// LevelDBKeyStore is a temporary solution for keeping hot node keys. +// The target state is an encrypted storage with password authentication. +type LevelDBKeyStore struct { + keystoreDb *leveldb.DB +} + +func NewLevelDBKeyStore(username, keystorePath string) (KeyStore, error) { + db, err := leveldb.OpenFile(keystorePath, nil) + if err != nil { + return nil, fmt.Errorf("failed to open keystore: %w", err) + } + + keystore := &LevelDBKeyStore{ + keystoreDb: db, + } + + if _, err := keystore.keystoreDb.Get([]byte(secretsKey), nil); err != nil { + if err := keystore.initJsonKey(secretsKey, map[string]*KeyPair{ + username: NewKeyPair(), + }); err != nil { + return nil, fmt.Errorf("failed to init %s storage: %w", operationsKey, err) + } + } + + return keystore, nil +} + +func (s *LevelDBKeyStore) LoadKeys(userName, password string) (*KeyPair, error) { + bz, err := s.keystoreDb.Get([]byte(secretsKey), nil) + if err != nil { + return nil, fmt.Errorf("failed to read keystore: %w", err) + } + + var keyPairs = map[string]*KeyPair{} + if err := json.Unmarshal(bz, &keyPairs); err != nil { + return nil, fmt.Errorf("failed to unmarshak key pairs: %w", err) + } + + keyPair, ok := keyPairs[userName] + if !ok { + return nil, fmt.Errorf("no key pair found for user %s", userName) + } + + return keyPair, nil +} + +func (s *LevelDBKeyStore) initJsonKey(key string, data interface{}) error { + if _, err := s.keystoreDb.Get([]byte(key), nil); err != nil { + operationsBz, err := json.Marshal(data) + if err != nil { + return fmt.Errorf("failed to marshal storage structure: %w", err) + } + err = s.keystoreDb.Put([]byte(key), operationsBz, nil) + if err != nil { + return fmt.Errorf("failed to init state: %w", err) + } + } + + return nil +} + +type KeyPair struct { + Pub ed25519.PublicKey + Priv ed25519.PrivateKey +} + +func NewKeyPair() *KeyPair { + // TODO: implement proper generation. + pub, priv, _ := ed25519.GenerateKey(nil) + return &KeyPair{ + Pub: pub, + Priv: priv, + } +} diff --git a/client/state.go b/client/state.go index c336a24..e9edcaf 100644 --- a/client/state.go +++ b/client/state.go @@ -45,15 +45,25 @@ func NewLevelDBState(stateDbPath string) (State, error) { } // Init state key for operations JSON. - if err := state.initJsonKey(operationsKey, map[string]*Operation{}); err != nil { - return nil, fmt.Errorf("failed to init %s storage: %w", operationsKey, err) + if _, err := state.stateDb.Get([]byte(operationsKey), nil); err != nil { + if err := state.initJsonKey(operationsKey, map[string]*Operation{}); err != nil { + return nil, fmt.Errorf("failed to init %s storage: %w", operationsKey, err) + } } // Init state key for offset bytes. - bz := make([]byte, 8) - binary.LittleEndian.PutUint64(bz, 0) - if err := db.Put([]byte(offsetKey), bz, nil); err != nil { - return nil, fmt.Errorf("failed to init %s storage: %w", offsetKey, err) + if _, err := state.stateDb.Get([]byte(offsetKey), nil); err != nil { + bz := make([]byte, 8) + binary.LittleEndian.PutUint64(bz, 0) + if err := db.Put([]byte(offsetKey), bz, nil); err != nil { + return nil, fmt.Errorf("failed to init %s storage: %w", offsetKey, err) + } + } + + if _, err := state.stateDb.Get([]byte(fsmStateKey), nil); err != nil { + if err := db.Put([]byte(fsmStateKey), []byte{}, nil); err != nil { + return nil, fmt.Errorf("failed to init %s storage: %w", offsetKey, err) + } } return state, nil diff --git a/client/types.go b/client/types.go index 27742f2..31c675b 100644 --- a/client/types.go +++ b/client/types.go @@ -4,8 +4,13 @@ import ( "bytes" "encoding/json" "fmt" - "github.com/depools/dc4bc/fsm/fsm" "time" + + "github.com/depools/dc4bc/fsm/fsm" + "github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm" + "github.com/depools/dc4bc/fsm/state_machines/signature_proposal_fsm" + "github.com/depools/dc4bc/fsm/types/requests" + "github.com/depools/dc4bc/storage" ) type OperationType string @@ -38,18 +43,42 @@ func (o *Operation) Check(o2 *Operation) error { return nil } -type FSMRequest struct { - Event fsm.Event - Args []interface{} -} - -func FSMRequestFromBytes(data []byte) (FSMRequest, error) { - var ( - r FSMRequest - err error - ) - if err = json.Unmarshal(data, &r); err != nil { - return r, err +func FSMRequestFromMessage(message storage.Message) (interface{}, error) { + var resolvedValue interface{} + switch fsm.Event(message.Event) { + case signature_proposal_fsm.EventInitProposal: + var req requests.SignatureProposalParticipantsListRequest + if err := json.Unmarshal(message.Data, &req); err != nil { + return fmt.Errorf("failed to unmarshal fsm req: %v", err), nil + } + resolvedValue = req + case dkg_proposal_fsm.EventDKGCommitConfirmationReceived: + var req requests.DKGProposalCommitConfirmationRequest + if err := json.Unmarshal(message.Data, &req); err != nil { + return fmt.Errorf("failed to unmarshal fsm req: %v", err), nil + } + resolvedValue = req + case dkg_proposal_fsm.EventDKGDealConfirmationReceived: + var req requests.DKGProposalDealConfirmationRequest + if err := json.Unmarshal(message.Data, &req); err != nil { + return fmt.Errorf("failed to unmarshal fsm req: %v", err), nil + } + resolvedValue = req + case dkg_proposal_fsm.EventDKGResponseConfirmationReceived: + var req requests.DKGProposalResponseConfirmationRequest + if err := json.Unmarshal(message.Data, &req); err != nil { + return fmt.Errorf("failed to unmarshal fsm req: %v", err), nil + } + resolvedValue = req + case dkg_proposal_fsm.EventDKGMasterKeyConfirmationReceived: + var req requests.DKGProposalMasterKeyConfirmationRequest + if err := json.Unmarshal(message.Data, &req); err != nil { + return fmt.Errorf("failed to unmarshal fsm req: %v", err), nil + } + resolvedValue = req + default: + return nil, fmt.Errorf("invalid event: %s", message.Event) } - return r, err + + return resolvedValue, nil } diff --git a/cmd/client/main.go b/cmd/client/main.go index 2f1c0af..ac17e83 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -15,21 +15,30 @@ import ( ) const ( + flagUserName = "username" flagListenAddr = "listen_addr" flagStateDBDSN = "state_dbdsn" flagStorageDBDSN = "storage_dbdsn" + flagStoreDBDSN = "key_store_dbdsn" ) func init() { - rootCmd.PersistentFlags().String(flagListenAddr, "localhost:8080", "Listen address") + rootCmd.PersistentFlags().String(flagUserName, "testUser", "Username") + rootCmd.PersistentFlags().String(flagListenAddr, "localhost:8080", "Listen Address") rootCmd.PersistentFlags().String(flagStateDBDSN, "./dc4bc_client_state", "State DBDSN") rootCmd.PersistentFlags().String(flagStorageDBDSN, "./dc4bc_file_storage", "Storage DBDSN") + rootCmd.PersistentFlags().String(flagStoreDBDSN, "./dc4bc_jey_store", "Key Store DBDSN") } var rootCmd = &cobra.Command{ Use: "dc4bc_client", Short: "dc4bc client implementation", Run: func(cmd *cobra.Command, args []string) { + userName, err := cmd.PersistentFlags().GetString(flagUserName) + if err != nil { + log.Fatalf("failed to read configuration: %v", err) + } + listenAddr, err := cmd.PersistentFlags().GetString(flagListenAddr) if err != nil { log.Fatalf("failed to read configuration: %v", err) @@ -45,6 +54,11 @@ var rootCmd = &cobra.Command{ log.Fatalf("failed to read configuration: %v", err) } + keyStoreDBDSN, err := cmd.PersistentFlags().GetString(flagStoreDBDSN) + if err != nil { + log.Fatalf("failed to read configuration: %v", err) + } + ctx := context.Background() ctx, cancel := context.WithCancel(ctx) @@ -58,11 +72,16 @@ var rootCmd = &cobra.Command{ log.Fatalf("Failed to init storage client: %v", err) } + keyStore, err := client.NewLevelDBKeyStore(userName, keyStoreDBDSN) + if err != nil { + log.Fatalf("Failed to init key store: %v", err) + } + processor := qr.NewCameraProcessor() // TODO: create state machine. - cli, err := client.NewClient(ctx, nil, state, stg, processor) + cli, err := client.NewClient(ctx, userName, nil, state, stg, keyStore, processor) if err != nil { log.Fatalf("Failed to init client: %v", err) } diff --git a/main.go b/main.go index 6998d5a..697894a 100644 --- a/main.go +++ b/main.go @@ -29,6 +29,7 @@ func main() { var numNodes = 4 for nodeID := 0; nodeID < numNodes; nodeID++ { var ctx = context.Background() + var userName = fmt.Sprintf("node_%d", nodeID) var state, err = client.NewLevelDBState(fmt.Sprintf("/tmp/dc4bc_node_%d_state", nodeID)) if err != nil { log.Fatalf("node %d failed to init state: %v\n", nodeID, err) @@ -39,11 +40,18 @@ func main() { log.Fatalf("node %d failed to init storage: %v\n", nodeID, err) } + keyStore, err := client.NewLevelDBKeyStore(userName, fmt.Sprintf("/tmp/dc4bc_node_%d_key_store", nodeID)) + if err != nil { + log.Fatalf("Failed to init key store: %v", err) + } + clt, err := client.NewClient( ctx, + userName, nil, state, stg, + keyStore, qr.NewCameraProcessor(), ) if err != nil { diff --git a/mocks/clientMocks/state_mock.go b/mocks/clientMocks/state_mock.go index c3b5bce..fbef32a 100644 --- a/mocks/clientMocks/state_mock.go +++ b/mocks/clientMocks/state_mock.go @@ -5,10 +5,9 @@ package clientMocks import ( - reflect "reflect" - client "github.com/depools/dc4bc/client" gomock "github.com/golang/mock/gomock" + reflect "reflect" ) // MockState is a mock of State interface @@ -72,7 +71,7 @@ func (m *MockState) SaveFSM(arg0 []byte) error { } // SaveFSM indicates an expected call of SaveFSM -func (mr *MockStateMockRecorder) SaveFSM(arg0 []byte) *gomock.Call { +func (mr *MockStateMockRecorder) SaveFSM(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveFSM", reflect.TypeOf((*MockState)(nil).SaveFSM), arg0) } diff --git a/mocks/gomock.go b/mocks/gomock.go index 3bada66..5cce861 100644 --- a/mocks/gomock.go +++ b/mocks/gomock.go @@ -1,5 +1,6 @@ package mocks //go:generate mockgen -source=./../client/state.go -destination=./clientMocks/state_mock.go -package=clientMocks +//go:generate mockgen -source=./../client/keystore.go -destination=./clientMocks/keystore_mock.go -package=clientMocks //go:generate mockgen -source=./../storage/types.go -destination=./storageMocks/storage_mock.go -package=storageMocks //go:generate mockgen -source=./../qr/qr.go -destination=./qrMocks/qr_mock.go -package=qrMocks diff --git a/mocks/storageMocks/storage_mock.go b/mocks/storageMocks/storage_mock.go index 642fae5..4fcf0d9 100644 --- a/mocks/storageMocks/storage_mock.go +++ b/mocks/storageMocks/storage_mock.go @@ -5,10 +5,9 @@ package storageMocks import ( - reflect "reflect" - storage "github.com/depools/dc4bc/storage" gomock "github.com/golang/mock/gomock" + reflect "reflect" ) // MockStorage is a mock of Storage interface diff --git a/storage/types.go b/storage/types.go index 583d614..ddc6e64 100644 --- a/storage/types.go +++ b/storage/types.go @@ -1,10 +1,31 @@ package storage +import ( + "bytes" + "crypto/ed25519" +) + type Message struct { - Data []byte `json:"data"` - Signature []byte `json:"signature"` ID string `json:"id"` Offset uint64 `json:"offset"` + Event string `json:"event"` + Data []byte `json:"data"` + Signature []byte `json:"signature"` + Sender 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() +} + +func (m *Message) Verify(pubKey ed25519.PublicKey) bool { + return ed25519.Verify(pubKey, m.Bytes(), m.Signature) } type Storage interface { From 8c398091cdb640e1cf3a419100f3615ce61aab17 Mon Sep 17 00:00:00 2001 From: Andrej Zavgorodnij Date: Tue, 18 Aug 2020 19:41:43 +0300 Subject: [PATCH 2/6] wip --- client/client.go | 74 ++++++++++++++++--- client/client_test.go | 32 ++++---- client/keystore.go | 5 ++ client/state.go | 49 ++++++++++-- fsm/state_machines/provider.go | 18 ++--- fsm/state_machines/provider_test.go | 11 +-- .../signature_proposal_fsm/actions.go | 5 +- .../signature_proposal_fsm/helpers.go | 7 +- fsm/types/requests/signature_proposal.go | 2 +- .../requests/signature_proposal_validation.go | 9 ++- mocks/clientMocks/state_mock.go | 24 +++--- storage/types.go | 16 ++-- 12 files changed, 176 insertions(+), 76 deletions(-) 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() From 039aef5c6dd4ee58e1e615a395e22122dfa19af0 Mon Sep 17 00:00:00 2001 From: Andrej Zavgorodnij Date: Tue, 18 Aug 2020 19:47:45 +0300 Subject: [PATCH 3/6] wip --- fsm/state_machines/internal/types.go | 9 ++-- .../signature_proposal_fsm/actions.go | 42 ++++--------------- .../signature_proposal_fsm/helpers.go | 2 - 3 files changed, 12 insertions(+), 41 deletions(-) diff --git a/fsm/state_machines/internal/types.go b/fsm/state_machines/internal/types.go index ea45971..27b713f 100644 --- a/fsm/state_machines/internal/types.go +++ b/fsm/state_machines/internal/types.go @@ -1,7 +1,6 @@ package internal import ( - "crypto/rsa" "time" ) @@ -15,12 +14,10 @@ type SignatureProposalParticipant struct { // Public title for address, such as name, nickname, organization ParticipantId int Title string - PubKey *rsa.PublicKey + PubKey []byte DkgPubKey []byte - // For validation user confirmation: sign(InvitationSecret, PubKey) => user - InvitationSecret string - Status ParticipantStatus - UpdatedAt time.Time + Status ParticipantStatus + UpdatedAt time.Time } // Unique alias for map iteration - Public Key Fingerprint diff --git a/fsm/state_machines/signature_proposal_fsm/actions.go b/fsm/state_machines/signature_proposal_fsm/actions.go index cebc4aa..3d1488f 100644 --- a/fsm/state_machines/signature_proposal_fsm/actions.go +++ b/fsm/state_machines/signature_proposal_fsm/actions.go @@ -1,7 +1,6 @@ package signature_proposal_fsm import ( - "crypto/x509" "errors" "fmt" "time" @@ -41,25 +40,13 @@ func (m *SignatureProposalFSM) actionInitSignatureProposal(inEvent fsm.Event, ar for index, participant := range request.Participants { participantId := createFingerprint(&participant.PubKey) - secret, err := generateRandomString(32) - if err != nil { - return inEvent, nil, errors.New("cannot generate source for {InvitationSecret}") - } - - parsedPubKey, err := x509.ParsePKCS1PublicKey(participant.PubKey) - - if err != nil { - return inEvent, nil, errors.New("cannot parse {PubKey}") - } - m.payload.SignatureProposalPayload.Quorum[participantId] = &internal.SignatureProposalParticipant{ - ParticipantId: index, - Title: participant.Addr, - PubKey: parsedPubKey, - DkgPubKey: participant.DkgPubKey, - InvitationSecret: secret, - Status: internal.SignatureConfirmationAwaitConfirmation, - UpdatedAt: request.CreatedAt, + ParticipantId: index, + Title: participant.Addr, + PubKey: participant.PubKey, + DkgPubKey: participant.DkgPubKey, + Status: internal.SignatureConfirmationAwaitConfirmation, + UpdatedAt: request.CreatedAt, } } @@ -74,15 +61,10 @@ func (m *SignatureProposalFSM) actionInitSignatureProposal(inEvent fsm.Event, ar responseData := make(responses.SignatureProposalParticipantInvitationsResponse, 0) for pubKeyFingerprint, proposal := range m.payload.SignatureProposalPayload.Quorum { - encryptedInvitationSecret, err := encryptWithPubKey(proposal.PubKey, proposal.InvitationSecret) - if err != nil { - return inEvent, nil, errors.New("cannot encryptWithPubKey") - } responseEntry := &responses.SignatureProposalParticipantInvitationEntry{ - ParticipantId: proposal.ParticipantId, - Title: proposal.Title, - PubKeyFingerprint: pubKeyFingerprint, - EncryptedInvitation: encryptedInvitationSecret, + ParticipantId: proposal.ParticipantId, + Title: proposal.Title, + PubKeyFingerprint: pubKeyFingerprint, } responseData = append(responseData, responseEntry) } @@ -118,12 +100,6 @@ func (m *SignatureProposalFSM) actionProposalResponseByParticipant(inEvent fsm.E } signatureProposalParticipant := m.payload.SigQuorumGet(request.PubKeyFingerprint) - - if signatureProposalParticipant.InvitationSecret != request.DecryptedInvitation { - err = errors.New("{InvitationSecret} not match {DecryptedInvitation}") - return - } - if signatureProposalParticipant.UpdatedAt.Add(config.SignatureProposalConfirmationDeadline).Before(request.CreatedAt) { outEvent = eventSetValidationCanceledByTimeout return diff --git a/fsm/state_machines/signature_proposal_fsm/helpers.go b/fsm/state_machines/signature_proposal_fsm/helpers.go index 69a7559..171db5c 100644 --- a/fsm/state_machines/signature_proposal_fsm/helpers.go +++ b/fsm/state_machines/signature_proposal_fsm/helpers.go @@ -18,8 +18,6 @@ func ProposalParticipantsQuorumToResponse(list *internal.SignatureProposalQuorum response = append(response, &responses.SignatureProposalParticipantInvitationEntry{ Title: participant.Title, PubKeyFingerprint: quorumId, - // TODO: Add encryption - EncryptedInvitation: participant.InvitationSecret, }) } return response From bd45e3d28b631d899d93c1a7922a5f7607ce4093 Mon Sep 17 00:00:00 2001 From: x88 Date: Wed, 19 Aug 2020 13:01:20 +0300 Subject: [PATCH 4/6] wip --- fsm/state_machines/signing_proposal_fsm/actions.go | 1 + fsm/state_machines/signing_proposal_fsm/init.go | 10 +++++++--- fsm/types/requests/requests.go | 7 +++++++ fsm/types/requests/requests_validation.go | 11 +++++++++++ 4 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 fsm/types/requests/requests.go create mode 100644 fsm/types/requests/requests_validation.go diff --git a/fsm/state_machines/signing_proposal_fsm/actions.go b/fsm/state_machines/signing_proposal_fsm/actions.go index e32fe13..7c19f60 100644 --- a/fsm/state_machines/signing_proposal_fsm/actions.go +++ b/fsm/state_machines/signing_proposal_fsm/actions.go @@ -16,6 +16,7 @@ func (m *SigningProposalFSM) actionInitSigningProposal(inEvent fsm.Event, args . m.payload.SigningProposalPayload = &internal.SigningConfirmation{ Quorum: make(internal.SigningProposalQuorum), + // CreatedAt: } for _, participant := range m.payload.SignatureProposalPayload.Quorum { diff --git a/fsm/state_machines/signing_proposal_fsm/init.go b/fsm/state_machines/signing_proposal_fsm/init.go index 92c80a4..7032d5f 100644 --- a/fsm/state_machines/signing_proposal_fsm/init.go +++ b/fsm/state_machines/signing_proposal_fsm/init.go @@ -23,10 +23,14 @@ const ( StateSigningAwaitPartialKeys = fsm.State("state_signing_await_partial_keys") // Cancelled - StateSigningPartialKeysAwaitCancelledByTimeout = fsm.State("state_signing_partial_keys_await_cancelled_by_timeout") - StateSigningPartialKeysAwaitCancelledByParticipant = fsm.State("state_signing_partial_keys_await_cancelled_by_participant") + StateSigningPartialKeysAwaitCancelledByTimeout = fsm.State("state_signing_partial_signatures_await_cancelled_by_timeout") + StateSigningPartialKeysAwaitCancelledByParticipant = fsm.State("state_signing_partial_signatures_await_cancelled_by_participant") - StateSigningPartialKeysCollected = fsm.State("state_signing_partial_keys_collected") + StateSigningPartialKeysCollected = fsm.State("state_signing_partial_signatures_collected") + + // await full + + // // Events diff --git a/fsm/types/requests/requests.go b/fsm/types/requests/requests.go new file mode 100644 index 0000000..35369a3 --- /dev/null +++ b/fsm/types/requests/requests.go @@ -0,0 +1,7 @@ +package requests + +import "time" + +type DefaultRequest struct { + CreatedAt time.Time +} diff --git a/fsm/types/requests/requests_validation.go b/fsm/types/requests/requests_validation.go new file mode 100644 index 0000000..05dc179 --- /dev/null +++ b/fsm/types/requests/requests_validation.go @@ -0,0 +1,11 @@ +package requests + +import "errors" + +func (r *DefaultRequest) Validate() error { + if r.CreatedAt.IsZero() { + return errors.New("{CreatedAt} is not set") + } + + return nil +} From 9fbc54cf3491de615c0a9ccaa0f1a4c07aced002 Mon Sep 17 00:00:00 2001 From: x88 Date: Wed, 19 Aug 2020 16:47:38 +0300 Subject: [PATCH 5/6] wip merged --- .../dkg_proposal_fsm/actions.go | 47 +++++++++++--- fsm/state_machines/internal/provider.go | 40 ++++++++++-- fsm/state_machines/internal/types.go | 27 ++++---- fsm/state_machines/provider.go | 18 +++--- fsm/state_machines/provider_test.go | 61 ++++++------------- .../signature_proposal_fsm/actions.go | 49 +++++++-------- .../signature_proposal_fsm/helpers.go | 5 +- .../signing_proposal_fsm/actions.go | 31 ++++++++-- fsm/types/requests/signature_proposal.go | 5 +- .../requests/signature_proposal_validation.go | 8 +-- fsm/types/responses/signature_proposal.go | 9 +-- 11 files changed, 174 insertions(+), 126 deletions(-) diff --git a/fsm/state_machines/dkg_proposal_fsm/actions.go b/fsm/state_machines/dkg_proposal_fsm/actions.go index 60ac93f..6c16cb5 100644 --- a/fsm/state_machines/dkg_proposal_fsm/actions.go +++ b/fsm/state_machines/dkg_proposal_fsm/actions.go @@ -14,21 +14,38 @@ import ( // Init func (m *DKGProposalFSM) actionInitDKGProposal(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { + m.payloadMu.Lock() + defer m.payloadMu.Unlock() + if m.payload.DKGProposalPayload != nil { return } - m.payload.DKGProposalPayload = &internal.DKGConfirmation{ - Quorum: make(internal.DKGProposalQuorum), + if len(args) != 1 { + err = errors.New("{arg0} required {DefaultRequest}") + return } - for _, participant := range m.payload.SignatureProposalPayload.Quorum { - m.payload.DKGProposalPayload.Quorum[participant.ParticipantId] = &internal.DKGProposalParticipant{ - Title: participant.Title, + request, ok := args[0].(requests.DefaultRequest) + + if !ok { + err = errors.New("cannot cast {arg0} to type {DefaultRequest}") + return + } + + m.payload.DKGProposalPayload = &internal.DKGConfirmation{ + Quorum: make(internal.DKGProposalQuorum), + CreatedAt: request.CreatedAt, + ExpiresAt: request.CreatedAt.Add(config.DkgConfirmationDeadline), + } + + for participantId, participant := range m.payload.SignatureProposalPayload.Quorum { + m.payload.DKGProposalPayload.Quorum[participantId] = &internal.DKGProposalParticipant{ + Addr: participant.Addr, Status: internal.CommitAwaitConfirmation, UpdatedAt: participant.UpdatedAt, } - copy(m.payload.DKGProposalPayload.Quorum[participant.ParticipantId].PubKey, participant.DkgPubKey) + copy(m.payload.DKGProposalPayload.Quorum[participantId].DkgPubKey, participant.DkgPubKey) } // Remove m.payload.SignatureProposalPayload? @@ -71,9 +88,11 @@ func (m *DKGProposalFSM) actionCommitConfirmationReceived(inEvent fsm.Event, arg } copy(dkgProposalParticipant.Commit, request.Commit) - dkgProposalParticipant.UpdatedAt = request.CreatedAt dkgProposalParticipant.Status = internal.CommitConfirmed + dkgProposalParticipant.UpdatedAt = request.CreatedAt + m.payload.SignatureProposalPayload.UpdatedAt = request.CreatedAt + m.payload.DKGQuorumUpdate(request.ParticipantId, dkgProposalParticipant) return @@ -163,9 +182,11 @@ func (m *DKGProposalFSM) actionDealConfirmationReceived(inEvent fsm.Event, args } copy(dkgProposalParticipant.Deal, request.Deal) - dkgProposalParticipant.UpdatedAt = request.CreatedAt dkgProposalParticipant.Status = internal.DealConfirmed + dkgProposalParticipant.UpdatedAt = request.CreatedAt + m.payload.SignatureProposalPayload.UpdatedAt = request.CreatedAt + m.payload.DKGQuorumUpdate(request.ParticipantId, dkgProposalParticipant) return @@ -255,9 +276,11 @@ func (m *DKGProposalFSM) actionResponseConfirmationReceived(inEvent fsm.Event, a } copy(dkgProposalParticipant.Response, request.Response) - dkgProposalParticipant.UpdatedAt = request.CreatedAt dkgProposalParticipant.Status = internal.ResponseConfirmed + dkgProposalParticipant.UpdatedAt = request.CreatedAt + m.payload.SignatureProposalPayload.UpdatedAt = request.CreatedAt + m.payload.DKGQuorumUpdate(request.ParticipantId, dkgProposalParticipant) return @@ -347,9 +370,11 @@ func (m *DKGProposalFSM) actionMasterKeyConfirmationReceived(inEvent fsm.Event, } copy(dkgProposalParticipant.MasterKey, request.MasterKey) - dkgProposalParticipant.UpdatedAt = request.CreatedAt dkgProposalParticipant.Status = internal.MasterKeyConfirmed + dkgProposalParticipant.UpdatedAt = request.CreatedAt + m.payload.SignatureProposalPayload.UpdatedAt = request.CreatedAt + m.payload.DKGQuorumUpdate(request.ParticipantId, dkgProposalParticipant) return @@ -527,7 +552,9 @@ func (m *DKGProposalFSM) actionConfirmationError(inEvent fsm.Event, args ...inte } dkgProposalParticipant.Error = request.Error + dkgProposalParticipant.UpdatedAt = request.CreatedAt + m.payload.SignatureProposalPayload.UpdatedAt = request.CreatedAt m.payload.DKGQuorumUpdate(request.ParticipantId, dkgProposalParticipant) diff --git a/fsm/state_machines/internal/provider.go b/fsm/state_machines/internal/provider.go index 2b3b177..7a0c5c1 100644 --- a/fsm/state_machines/internal/provider.go +++ b/fsm/state_machines/internal/provider.go @@ -1,6 +1,11 @@ package internal -import "github.com/depools/dc4bc/fsm/fsm_pool" +import ( + "crypto/ed25519" + "encoding/hex" + "errors" + "github.com/depools/dc4bc/fsm/fsm_pool" +) type DumpedMachineProvider interface { fsm_pool.MachineProvider @@ -10,10 +15,11 @@ type DumpedMachineProvider interface { // DKG and other stages quorums are separated, // because unnecessary data may be unset type DumpedMachineStatePayload struct { - TransactionId string + DkgId string SignatureProposalPayload *SignatureConfirmation DKGProposalPayload *DKGConfirmation SigningProposalPayload *SigningConfirmation + pubKeys map[string]ed25519.PublicKey } // Signature quorum @@ -26,7 +32,7 @@ func (p *DumpedMachineStatePayload) SigQuorumCount() int { return count } -func (p *DumpedMachineStatePayload) SigQuorumExists(id string) bool { +func (p *DumpedMachineStatePayload) SigQuorumExists(id int) bool { var exists bool if p.SignatureProposalPayload.Quorum != nil { _, exists = p.SignatureProposalPayload.Quorum[id] @@ -34,14 +40,14 @@ func (p *DumpedMachineStatePayload) SigQuorumExists(id string) bool { return exists } -func (p *DumpedMachineStatePayload) SigQuorumGet(id string) (participant *SignatureProposalParticipant) { +func (p *DumpedMachineStatePayload) SigQuorumGet(id int) (participant *SignatureProposalParticipant) { if p.SignatureProposalPayload.Quorum != nil { participant, _ = p.SignatureProposalPayload.Quorum[id] } return } -func (p *DumpedMachineStatePayload) SigQuorumUpdate(id string, participant *SignatureProposalParticipant) { +func (p *DumpedMachineStatePayload) SigQuorumUpdate(id int, participant *SignatureProposalParticipant) { if p.SignatureProposalPayload.Quorum != nil { p.SignatureProposalPayload.Quorum[id] = participant } @@ -111,3 +117,27 @@ func (p *DumpedMachineStatePayload) SigningQuorumUpdate(id int, participant *Sig } return } + +func (p *DumpedMachineStatePayload) SetAddrHexPubKey(addr string, key ed25519.PublicKey) { + if p.pubKeys == nil { + p.pubKeys = make(map[string]ed25519.PublicKey) + } + hexKey := hex.EncodeToString([]byte(addr)) + p.pubKeys[hexKey] = key + return +} + +func (p *DumpedMachineStatePayload) GetPubKeyByAddr(addr string) (ed25519.PublicKey, error) { + if p.pubKeys == nil { + return nil, errors.New("{pubKeys} not initialized") + } + if addr == "" { + return nil, errors.New("{addr} cannot be empty") + } + pubKey, ok := p.pubKeys[addr] + if !ok { + return nil, errors.New("cannot find public key by {addr}") + } + + return pubKey, nil +} diff --git a/fsm/state_machines/internal/types.go b/fsm/state_machines/internal/types.go index 9b6d056..62c21bd 100644 --- a/fsm/state_machines/internal/types.go +++ b/fsm/state_machines/internal/types.go @@ -1,7 +1,7 @@ package internal import ( - "crypto/rsa" + "crypto/ed25519" "time" ) @@ -32,24 +32,23 @@ func (s ConfirmationParticipantStatus) String() string { type SignatureConfirmation struct { Quorum SignatureProposalQuorum CreatedAt time.Time + UpdatedAt time.Time ExpiresAt time.Time } type SignatureProposalParticipant struct { - // Public title for address, such as name, nickname, organization - ParticipantId int - Title string - PubKey []byte - DkgPubKey []byte + Addr string + PubKey ed25519.PublicKey + DkgPubKey []byte // For validation user confirmation: sign(InvitationSecret, PubKey) => user InvitationSecret string - Status ParticipantStatus + Status ConfirmationParticipantStatus UpdatedAt time.Time } // Unique alias for map iteration - Public Key Fingerprint // Excludes array merge and rotate operations -type SignatureProposalQuorum map[string]*SignatureProposalParticipant +type SignatureProposalQuorum map[int]*SignatureProposalParticipant // DKG proposal @@ -71,8 +70,8 @@ const ( ) type DKGProposalParticipant struct { - Title string - PubKey []byte + Addr string + DkgPubKey []byte Commit []byte Deal []byte Response []byte @@ -86,8 +85,9 @@ type DKGProposalQuorum map[int]*DKGProposalParticipant type DKGConfirmation struct { Quorum DKGProposalQuorum - CreatedAt *time.Time - ExpiresAt *time.Time + CreatedAt time.Time + UpdatedAt time.Time + ExpiresAt time.Time } type DKGProposalParticipantStatus uint8 @@ -131,6 +131,7 @@ type SigningConfirmation struct { SrcPayload []byte EncryptedPayload []byte CreatedAt time.Time + UpdatedAt time.Time ExpiresAt time.Time } @@ -171,7 +172,7 @@ func (s SigningParticipantStatus) String() string { } type SigningProposalParticipant struct { - Title string + Addr string Status SigningParticipantStatus PartialKey []byte Error error diff --git a/fsm/state_machines/provider.go b/fsm/state_machines/provider.go index e948717..e4742d3 100644 --- a/fsm/state_machines/provider.go +++ b/fsm/state_machines/provider.go @@ -87,7 +87,11 @@ func FromDump(data []byte) (*FSMInstance, error) { } func (i *FSMInstance) GetPubKeyByAddr(addr string) (ed25519.PublicKey, error) { - return ed25519.PublicKey{}, nil + if i.dump == nil { + return nil, errors.New("dump not initialized") + } + + return i.dump.Payload.GetPubKeyByAddr(addr) } func (i *FSMInstance) Do(event fsm.Event, args ...interface{}) (result *fsm.Response, dump []byte, err error) { @@ -112,22 +116,22 @@ func (i *FSMInstance) Do(event fsm.Event, args ...interface{}) (result *fsm.Resp return result, dump, err } -func (i *FSMInstance) InitDump(transactionId string) error { +func (i *FSMInstance) InitDump(dkgID string) error { if i.dump != nil { return errors.New("dump already initialized") } - transactionId = strings.TrimSpace(transactionId) + dkgID = strings.TrimSpace(dkgID) - if transactionId == "" { - return errors.New("empty transaction id") + if dkgID == "" { + return errors.New("empty {dkgID}") } i.dump = &FSMDump{ - TransactionId: transactionId, + TransactionId: dkgID, State: fsm.StateGlobalIdle, Payload: &internal.DumpedMachineStatePayload{ - TransactionId: transactionId, + DkgId: dkgID, SignatureProposalPayload: nil, DKGProposalPayload: nil, }, diff --git a/fsm/state_machines/provider_test.go b/fsm/state_machines/provider_test.go index 80a8b0c..98b4eb4 100644 --- a/fsm/state_machines/provider_test.go +++ b/fsm/state_machines/provider_test.go @@ -3,9 +3,7 @@ package state_machines import ( "crypto/rand" "crypto/rsa" - "crypto/sha1" "crypto/x509" - "encoding/base64" "fmt" "log" "testing" @@ -19,7 +17,7 @@ import ( ) type testExternalParticipants struct { - Title string + Addr string PrivKey *rsa.PrivateKey PubKey *rsa.PublicKey DkgPubKey []byte @@ -28,7 +26,9 @@ type testExternalParticipants struct { var ( tm = time.Now() - testParticipants = map[string]*testExternalParticipants{} + dkgId = "1b7a6382afe0fbe2ff127a5779f5e9b042e685cabefeadcf4ef27c6089a56bfb" + + testParticipants = map[int]*testExternalParticipants{} testParticipantsListRequest = requests.SignatureProposalParticipantsListRequest{ Participants: []*requests.SignatureProposalParticipantsEntry{}, @@ -52,22 +52,17 @@ func init() { key.Precompute() - marshaledPubKey := x509.MarshalPKCS1PublicKey(&key.PublicKey) - hash := sha1.Sum(marshaledPubKey) - - fingerprint := base64.StdEncoding.EncodeToString(hash[:]) - pubKeyMock := make([]byte, 128) rand.Read(pubKeyMock) participant := &testExternalParticipants{ - Title: fmt.Sprintf("User %d", i), + Addr: fmt.Sprintf("User %d", i), PrivKey: key, PubKey: &key.PublicKey, DkgPubKey: pubKeyMock, } - testParticipants[fingerprint] = participant + testParticipants[i] = participant } participantsForRequest := make([]*requests.SignatureProposalParticipantsEntry, 0) @@ -75,7 +70,7 @@ func init() { for _, participant := range testParticipants { participantsForRequest = append(participantsForRequest, &requests.SignatureProposalParticipantsEntry{ - Addr: participant.Title, + Addr: participant.Addr, PubKey: x509.MarshalPKCS1PublicKey(participant.PubKey), DkgPubKey: participant.DkgPubKey, }) @@ -85,7 +80,7 @@ func init() { } func TestCreate_Positive(t *testing.T) { - testFSMInstance, err := Create() + testFSMInstance, err := Create(dkgId) if err != nil { t.Fatalf("expected nil error, got {%s}", err) } @@ -128,7 +123,7 @@ func compareState(t *testing.T, expected fsm.State, got fsm.State) { // Test Workflow func Test_SignatureProposal_Init(t *testing.T) { - testFSMInstance, err := Create() + testFSMInstance, err := Create(dkgId) compareErrNil(t, err) @@ -187,18 +182,10 @@ func Test_SignatureProposal_Positive(t *testing.T) { t.Fatalf("expected unique {ParticipantId}") } - if participant.Title == "" { + if participant.Addr == "" { t.Fatalf("expected not empty {Addr}") } - if participant.EncryptedInvitation == "" { - t.Fatalf("expected not empty {DecryptedInvitation}") - } - - if participant.PubKeyFingerprint == "" { - t.Fatalf("expected not empty {PubKeyFingerprint}") - } - participantsMap[participant.ParticipantId] = participant } @@ -216,21 +203,9 @@ func Test_SignatureProposal_Positive(t *testing.T) { compareFSMInstanceNotNil(t, testFSMInstance) - if _, ok := testParticipants[participant.PubKeyFingerprint]; !ok { - t.Fatalf("not found external user data for response fingerprint") - } - - r := rand.Reader - encrypted, err := rsa.DecryptPKCS1v15(r, testParticipants[participant.PubKeyFingerprint].PrivKey, []byte(participant.EncryptedInvitation)) - - if err != nil { - t.Fatalf("cannot encrypt {DecryptedInvitation} with private key") - } - fsmResponse, dump, err = testFSMInstance.Do(spf.EventConfirmSignatureProposal, requests.SignatureProposalParticipantRequest{ - PubKeyFingerprint: participant.PubKeyFingerprint, - DecryptedInvitation: string(encrypted), - CreatedAt: tm, + ParticipantId: participant.ParticipantId, + CreatedAt: tm, }) compareErrNil(t, err) @@ -249,7 +224,9 @@ func Test_SignatureProposal_Positive(t *testing.T) { testFSMInstance, err = FromDump(dump) - fsmResponse, dump, err = testFSMInstance.Do(dpf.EventDKGInitProcess) + fsmResponse, dump, err = testFSMInstance.Do(dpf.EventDKGInitProcess, requests.DefaultRequest{ + CreatedAt: time.Now(), + }) compareErrNil(t, err) @@ -269,7 +246,7 @@ func Test_SignatureProposal_Positive(t *testing.T) { compareFSMInstanceNotNil(t, testFSMInstance) - if _, ok := testParticipants[participant.PubKeyFingerprint]; !ok { + if _, ok := testParticipants[participant.ParticipantId]; !ok { t.Fatalf("not found external user data for response fingerprint") } @@ -305,7 +282,7 @@ func Test_SignatureProposal_Positive(t *testing.T) { compareFSMInstanceNotNil(t, testFSMInstance) - if _, ok := testParticipants[participant.PubKeyFingerprint]; !ok { + if _, ok := testParticipants[participant.ParticipantId]; !ok { t.Fatalf("not found external user data for response fingerprint") } @@ -341,7 +318,7 @@ func Test_SignatureProposal_Positive(t *testing.T) { compareFSMInstanceNotNil(t, testFSMInstance) - if _, ok := testParticipants[participant.PubKeyFingerprint]; !ok { + if _, ok := testParticipants[participant.ParticipantId]; !ok { t.Fatalf("not found external user data for response fingerprint") } @@ -383,7 +360,7 @@ func Test_SignatureProposal_Positive(t *testing.T) { compareFSMInstanceNotNil(t, testFSMInstance) - if _, ok := testParticipants[participant.PubKeyFingerprint]; !ok { + if _, ok := testParticipants[participant.ParticipantId]; !ok { t.Fatalf("not found external user data for response fingerprint") } diff --git a/fsm/state_machines/signature_proposal_fsm/actions.go b/fsm/state_machines/signature_proposal_fsm/actions.go index 9c2fe48..86f7e7b 100644 --- a/fsm/state_machines/signature_proposal_fsm/actions.go +++ b/fsm/state_machines/signature_proposal_fsm/actions.go @@ -35,19 +35,22 @@ func (m *SignatureProposalFSM) actionInitSignatureProposal(inEvent fsm.Event, ar } m.payload.SignatureProposalPayload = &internal.SignatureConfirmation{ - Quorum: make(internal.SignatureProposalQuorum), + Quorum: make(internal.SignatureProposalQuorum), + CreatedAt: request.CreatedAt, + ExpiresAt: request.CreatedAt.Add(config.SignatureProposalConfirmationDeadline), } for index, participant := range request.Participants { - participantId := createFingerprint(&participant.PubKey) - m.payload.SignatureProposalPayload.Quorum[participantId] = &internal.SignatureProposalParticipant{ - ParticipantId: index, - Title: participant.Addr, - PubKey: participant.PubKey, - DkgPubKey: participant.DkgPubKey, - Status: internal.SignatureConfirmationAwaitConfirmation, - UpdatedAt: request.CreatedAt, + //participantId := createFingerprint(&participant.DkgPubKey) + m.payload.SignatureProposalPayload.Quorum[index] = &internal.SignatureProposalParticipant{ + Addr: participant.Addr, + PubKey: participant.PubKey, + DkgPubKey: participant.DkgPubKey, + Status: internal.SigConfirmationAwaitConfirmation, + UpdatedAt: request.CreatedAt, } + + m.payload.SetAddrHexPubKey(participant.Addr, participant.PubKey) } // Checking fo quorum length @@ -60,11 +63,10 @@ func (m *SignatureProposalFSM) actionInitSignatureProposal(inEvent fsm.Event, ar responseData := make(responses.SignatureProposalParticipantInvitationsResponse, 0) - for pubKeyFingerprint, proposal := range m.payload.SignatureProposalPayload.Quorum { + for participantId, proposal := range m.payload.SignatureProposalPayload.Quorum { responseEntry := &responses.SignatureProposalParticipantInvitationEntry{ - ParticipantId: proposal.ParticipantId, - Title: proposal.Title, - PubKeyFingerprint: pubKeyFingerprint, + ParticipantId: participantId, + Addr: proposal.Addr, } responseData = append(responseData, responseEntry) } @@ -94,12 +96,12 @@ func (m *SignatureProposalFSM) actionProposalResponseByParticipant(inEvent fsm.E return } - if !m.payload.SigQuorumExists(request.PubKeyFingerprint) { - err = errors.New("{PubKeyFingerprint} not exist in quorum") + if !m.payload.SigQuorumExists(request.ParticipantId) { + err = errors.New("{ParticipantId} not exist in quorum") return } - signatureProposalParticipant := m.payload.SigQuorumGet(request.PubKeyFingerprint) + signatureProposalParticipant := m.payload.SigQuorumGet(request.ParticipantId) if signatureProposalParticipant.UpdatedAt.Add(config.SignatureProposalConfirmationDeadline).Before(request.CreatedAt) { outEvent = eventSetValidationCanceledByTimeout return @@ -122,7 +124,7 @@ func (m *SignatureProposalFSM) actionProposalResponseByParticipant(inEvent fsm.E signatureProposalParticipant.UpdatedAt = request.CreatedAt - m.payload.SigQuorumUpdate(request.PubKeyFingerprint, signatureProposalParticipant) + m.payload.SigQuorumUpdate(request.ParticipantId, signatureProposalParticipant) return } @@ -169,11 +171,10 @@ func (m *SignatureProposalFSM) actionValidateSignatureProposal(inEvent fsm.Event responseData := make(responses.SignatureProposalParticipantStatusResponse, 0) - for _, participant := range m.payload.SignatureProposalPayload.Quorum { + for participantId, participant := range m.payload.SignatureProposalPayload.Quorum { responseEntry := &responses.SignatureProposalParticipantStatusEntry{ - ParticipantId: participant.ParticipantId, - Title: participant.Title, - DkgPubKey: participant.DkgPubKey, + ParticipantId: participantId, + Addr: participant.Addr, Status: uint8(participant.Status), } responseData = append(responseData, responseEntry) @@ -188,10 +189,10 @@ func (m *SignatureProposalFSM) actionSignatureProposalCanceledByTimeout(inEvent responseData := make(responses.SignatureProposalParticipantStatusResponse, 0) - for _, participant := range m.payload.SignatureProposalPayload.Quorum { + for participantId, participant := range m.payload.SignatureProposalPayload.Quorum { responseEntry := &responses.SignatureProposalParticipantStatusEntry{ - ParticipantId: participant.ParticipantId, - Title: participant.Title, + ParticipantId: participantId, + Addr: participant.Addr, Status: uint8(participant.Status), } responseData = append(responseData, responseEntry) diff --git a/fsm/state_machines/signature_proposal_fsm/helpers.go b/fsm/state_machines/signature_proposal_fsm/helpers.go index 171db5c..9237973 100644 --- a/fsm/state_machines/signature_proposal_fsm/helpers.go +++ b/fsm/state_machines/signature_proposal_fsm/helpers.go @@ -14,10 +14,9 @@ import ( func ProposalParticipantsQuorumToResponse(list *internal.SignatureProposalQuorum) responses.SignatureProposalParticipantInvitationsResponse { var response responses.SignatureProposalParticipantInvitationsResponse - for quorumId, participant := range *list { + for _, participant := range *list { response = append(response, &responses.SignatureProposalParticipantInvitationEntry{ - Title: participant.Title, - PubKeyFingerprint: quorumId, + Addr: participant.Addr, }) } return response diff --git a/fsm/state_machines/signing_proposal_fsm/actions.go b/fsm/state_machines/signing_proposal_fsm/actions.go index 7c19f60..5cb8712 100644 --- a/fsm/state_machines/signing_proposal_fsm/actions.go +++ b/fsm/state_machines/signing_proposal_fsm/actions.go @@ -14,14 +14,31 @@ func (m *SigningProposalFSM) actionInitSigningProposal(inEvent fsm.Event, args . m.payloadMu.Lock() defer m.payloadMu.Unlock() - m.payload.SigningProposalPayload = &internal.SigningConfirmation{ - Quorum: make(internal.SigningProposalQuorum), - // CreatedAt: + if len(args) != 1 { + err = errors.New("{arg0} required {DefaultRequest}") + return } - for _, participant := range m.payload.SignatureProposalPayload.Quorum { - m.payload.SigningProposalPayload.Quorum[participant.ParticipantId] = &internal.SigningProposalParticipant{ - Title: participant.Title, + request, ok := args[0].(requests.DefaultRequest) + + if !ok { + err = errors.New("cannot cast {arg0} to type {DefaultRequest}") + return + } + + if err = request.Validate(); err != nil { + return + } + + m.payload.SigningProposalPayload = &internal.SigningConfirmation{ + Quorum: make(internal.SigningProposalQuorum), + CreatedAt: request.CreatedAt, + ExpiresAt: request.CreatedAt.Add(config.SigningConfirmationDeadline), + } + + for participantId, participant := range m.payload.SignatureProposalPayload.Quorum { + m.payload.SigningProposalPayload.Quorum[participantId] = &internal.SigningProposalParticipant{ + Addr: participant.Addr, Status: internal.SigningIdle, UpdatedAt: participant.UpdatedAt, } @@ -50,6 +67,8 @@ func (m *SigningProposalFSM) actionStartSigningProposal(inEvent fsm.Event, args return } + m.payload.SigningProposalPayload.CreatedAt = request.CreatedAt + return } diff --git a/fsm/types/requests/signature_proposal.go b/fsm/types/requests/signature_proposal.go index 9bc70e6..0ead4d2 100644 --- a/fsm/types/requests/signature_proposal.go +++ b/fsm/types/requests/signature_proposal.go @@ -24,7 +24,6 @@ type SignatureProposalParticipantsEntry struct { // "event_sig_proposal_decline_by_participant" type SignatureProposalParticipantRequest struct { // Key for link invitations to participants - PubKeyFingerprint string - DecryptedInvitation string - CreatedAt time.Time + ParticipantId int + CreatedAt time.Time } diff --git a/fsm/types/requests/signature_proposal_validation.go b/fsm/types/requests/signature_proposal_validation.go index 1821ab7..1bb25c8 100644 --- a/fsm/types/requests/signature_proposal_validation.go +++ b/fsm/types/requests/signature_proposal_validation.go @@ -46,12 +46,8 @@ func (r *SignatureProposalParticipantsListRequest) Validate() error { } func (r *SignatureProposalParticipantRequest) Validate() error { - if len(r.PubKeyFingerprint) == 0 { - return errors.New("{PubKeyFingerprint} cannot zero length") - } - - if len(r.DecryptedInvitation) == 0 { - return errors.New("{DecryptedInvitation} cannot zero length") + if r.ParticipantId < 0 { + return errors.New("{ParticipantId} cannot be a negative number") } if r.CreatedAt.IsZero() { diff --git a/fsm/types/responses/signature_proposal.go b/fsm/types/responses/signature_proposal.go index 22375f1..065447a 100644 --- a/fsm/types/responses/signature_proposal.go +++ b/fsm/types/responses/signature_proposal.go @@ -10,11 +10,7 @@ type SignatureProposalParticipantInvitationsResponse []*SignatureProposalPartici type SignatureProposalParticipantInvitationEntry struct { ParticipantId int // Public title for address, such as name, nickname, organization - Title string - // Key for link invitations to participants - PubKeyFingerprint string - // Encrypted with public key secret - EncryptedInvitation string + Addr string } // Public lists for proposal confirmation process @@ -23,7 +19,6 @@ type SignatureProposalParticipantStatusResponse []*SignatureProposalParticipantS type SignatureProposalParticipantStatusEntry struct { ParticipantId int - Title string - DkgPubKey []byte + Addr string Status uint8 } From 0037421ff02e58c372262bd4e10a55c96a037f43 Mon Sep 17 00:00:00 2001 From: x88 Date: Wed, 19 Aug 2020 17:26:10 +0300 Subject: [PATCH 6/6] wip --- .../dkg_proposal_fsm/actions.go | 109 ++++++------------ fsm/state_machines/internal/provider.go | 18 +-- fsm/state_machines/internal/types.go | 12 ++ .../signature_proposal_fsm/actions.go | 29 ++--- 4 files changed, 69 insertions(+), 99 deletions(-) diff --git a/fsm/state_machines/dkg_proposal_fsm/actions.go b/fsm/state_machines/dkg_proposal_fsm/actions.go index 6c16cb5..4373ae1 100644 --- a/fsm/state_machines/dkg_proposal_fsm/actions.go +++ b/fsm/state_machines/dkg_proposal_fsm/actions.go @@ -8,7 +8,6 @@ import ( "github.com/depools/dc4bc/fsm/state_machines/internal" "github.com/depools/dc4bc/fsm/types/requests" "reflect" - "time" ) // Init @@ -100,26 +99,23 @@ func (m *DKGProposalFSM) actionCommitConfirmationReceived(inEvent fsm.Event, arg func (m *DKGProposalFSM) actionValidateDkgProposalAwaitCommits(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { var ( - isContainsError, isContainsExpired bool + isContainsError bool ) m.payloadMu.Lock() defer m.payloadMu.Unlock() - tm := time.Now() + if m.payload.DKGProposalPayload.IsExpired() { + outEvent = eventDKGCommitsConfirmationCancelByErrorInternal + return + } unconfirmedParticipants := m.payload.DKGQuorumCount() for _, participant := range m.payload.DKGProposalPayload.Quorum { - if participant.Status == internal.CommitAwaitConfirmation { - if participant.UpdatedAt.Add(config.DkgConfirmationDeadline).Before(tm) { - isContainsExpired = true - } - } else { - if participant.Status == internal.CommitConfirmationError { - isContainsError = true - } else if participant.Status == internal.CommitConfirmed { - unconfirmedParticipants-- - } + if participant.Status == internal.CommitConfirmationError { + isContainsError = true + } else if participant.Status == internal.CommitConfirmed { + unconfirmedParticipants-- } } @@ -128,11 +124,6 @@ func (m *DKGProposalFSM) actionValidateDkgProposalAwaitCommits(inEvent fsm.Event return } - if isContainsExpired { - outEvent = eventDKGCommitsConfirmationCancelByErrorInternal - return - } - // The are no declined and timed out participants, check for all confirmations if unconfirmedParticipants > 0 { return @@ -194,26 +185,23 @@ func (m *DKGProposalFSM) actionDealConfirmationReceived(inEvent fsm.Event, args func (m *DKGProposalFSM) actionValidateDkgProposalAwaitDeals(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { var ( - isContainsError, isContainsExpired bool + isContainsError bool ) m.payloadMu.Lock() defer m.payloadMu.Unlock() - tm := time.Now() + if m.payload.DKGProposalPayload.IsExpired() { + outEvent = eventDKGDealsConfirmationCancelByTimeoutInternal + return + } unconfirmedParticipants := m.payload.DKGQuorumCount() for _, participant := range m.payload.DKGProposalPayload.Quorum { - if participant.Status == internal.DealAwaitConfirmation { - if participant.UpdatedAt.Add(config.DkgConfirmationDeadline).Before(tm) { - isContainsExpired = true - } - } else { - if participant.Status == internal.DealConfirmationError { - isContainsError = true - } else if participant.Status == internal.DealConfirmed { - unconfirmedParticipants-- - } + if participant.Status == internal.DealConfirmationError { + isContainsError = true + } else if participant.Status == internal.DealConfirmed { + unconfirmedParticipants-- } } @@ -222,11 +210,6 @@ func (m *DKGProposalFSM) actionValidateDkgProposalAwaitDeals(inEvent fsm.Event, return } - if isContainsExpired { - outEvent = eventDKGDealsConfirmationCancelByTimeoutInternal - return - } - // The are no declined and timed out participants, check for all confirmations if unconfirmedParticipants > 0 { return @@ -288,26 +271,23 @@ func (m *DKGProposalFSM) actionResponseConfirmationReceived(inEvent fsm.Event, a func (m *DKGProposalFSM) actionValidateDkgProposalAwaitResponses(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { var ( - isContainsError, isContainsExpired bool + isContainsError bool ) m.payloadMu.Lock() defer m.payloadMu.Unlock() - tm := time.Now() + if m.payload.DKGProposalPayload.IsExpired() { + outEvent = eventDKGResponseConfirmationCancelByTimeoutInternal + return + } unconfirmedParticipants := m.payload.DKGQuorumCount() for _, participant := range m.payload.DKGProposalPayload.Quorum { - if participant.Status == internal.ResponseAwaitConfirmation { - if participant.UpdatedAt.Add(config.DkgConfirmationDeadline).Before(tm) { - isContainsExpired = true - } - } else { - if participant.Status == internal.ResponseConfirmationError { - isContainsError = true - } else if participant.Status == internal.ResponseConfirmed { - unconfirmedParticipants-- - } + if participant.Status == internal.ResponseConfirmationError { + isContainsError = true + } else if participant.Status == internal.ResponseConfirmed { + unconfirmedParticipants-- } } @@ -316,11 +296,6 @@ func (m *DKGProposalFSM) actionValidateDkgProposalAwaitResponses(inEvent fsm.Eve return } - if isContainsExpired { - outEvent = eventDKGResponseConfirmationCancelByTimeoutInternal - return - } - // The are no declined and timed out participants, check for all confirmations if unconfirmedParticipants > 0 { return @@ -382,29 +357,26 @@ func (m *DKGProposalFSM) actionMasterKeyConfirmationReceived(inEvent fsm.Event, func (m *DKGProposalFSM) actionValidateDkgProposalAwaitMasterKey(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { var ( - isContainsError, isContainsExpired bool - masterKeys [][]byte + isContainsError bool + masterKeys [][]byte ) m.payloadMu.Lock() defer m.payloadMu.Unlock() - tm := time.Now() + if m.payload.DKGProposalPayload.IsExpired() { + outEvent = eventDKGMasterKeyConfirmationCancelByTimeoutInternal + return + } unconfirmedParticipants := m.payload.DKGQuorumCount() for _, participant := range m.payload.DKGProposalPayload.Quorum { - if participant.Status == internal.MasterKeyAwaitConfirmation { - if participant.UpdatedAt.Add(config.DkgConfirmationDeadline).Before(tm) { - isContainsExpired = true - } - } else { - if participant.Status == internal.MasterKeyConfirmationError { - isContainsError = true - } else if participant.Status == internal.MasterKeyConfirmed { - masterKeys = append(masterKeys, participant.MasterKey) - unconfirmedParticipants-- - } + if participant.Status == internal.MasterKeyConfirmationError { + isContainsError = true + } else if participant.Status == internal.MasterKeyConfirmed { + masterKeys = append(masterKeys, participant.MasterKey) + unconfirmedParticipants-- } } @@ -413,11 +385,6 @@ func (m *DKGProposalFSM) actionValidateDkgProposalAwaitMasterKey(inEvent fsm.Eve return } - if isContainsExpired { - outEvent = eventDKGMasterKeyConfirmationCancelByTimeoutInternal - return - } - // Temporary simplest match master keys if len(masterKeys) > 1 { for i, masterKey := range masterKeys { diff --git a/fsm/state_machines/internal/provider.go b/fsm/state_machines/internal/provider.go index 7a0c5c1..78969e9 100644 --- a/fsm/state_machines/internal/provider.go +++ b/fsm/state_machines/internal/provider.go @@ -19,7 +19,7 @@ type DumpedMachineStatePayload struct { SignatureProposalPayload *SignatureConfirmation DKGProposalPayload *DKGConfirmation SigningProposalPayload *SigningConfirmation - pubKeys map[string]ed25519.PublicKey + PubKeys map[string]ed25519.PublicKey } // Signature quorum @@ -118,23 +118,23 @@ func (p *DumpedMachineStatePayload) SigningQuorumUpdate(id int, participant *Sig return } -func (p *DumpedMachineStatePayload) SetAddrHexPubKey(addr string, key ed25519.PublicKey) { - if p.pubKeys == nil { - p.pubKeys = make(map[string]ed25519.PublicKey) +func (p *DumpedMachineStatePayload) SetAddrHexPubKey(addr string, pubKey ed25519.PublicKey) { + if p.PubKeys == nil { + p.PubKeys = make(map[string]ed25519.PublicKey) } - hexKey := hex.EncodeToString([]byte(addr)) - p.pubKeys[hexKey] = key + hexAddr := hex.EncodeToString([]byte(addr)) + p.PubKeys[hexAddr] = pubKey return } func (p *DumpedMachineStatePayload) GetPubKeyByAddr(addr string) (ed25519.PublicKey, error) { - if p.pubKeys == nil { - return nil, errors.New("{pubKeys} not initialized") + if p.PubKeys == nil { + return nil, errors.New("{PubKeys} not initialized") } if addr == "" { return nil, errors.New("{addr} cannot be empty") } - pubKey, ok := p.pubKeys[addr] + pubKey, ok := p.PubKeys[addr] if !ok { return nil, errors.New("cannot find public key by {addr}") } diff --git a/fsm/state_machines/internal/types.go b/fsm/state_machines/internal/types.go index 62c21bd..6d35b61 100644 --- a/fsm/state_machines/internal/types.go +++ b/fsm/state_machines/internal/types.go @@ -46,6 +46,10 @@ type SignatureProposalParticipant struct { UpdatedAt time.Time } +func (c *SignatureConfirmation) IsExpired() bool { + return c.ExpiresAt.Before(c.UpdatedAt) +} + // Unique alias for map iteration - Public Key Fingerprint // Excludes array merge and rotate operations type SignatureProposalQuorum map[int]*SignatureProposalParticipant @@ -90,6 +94,10 @@ type DKGConfirmation struct { ExpiresAt time.Time } +func (c *DKGConfirmation) IsExpired() bool { + return c.ExpiresAt.Before(c.UpdatedAt) +} + type DKGProposalParticipantStatus uint8 func (s DKGParticipantStatus) String() string { @@ -135,6 +143,10 @@ type SigningConfirmation struct { ExpiresAt time.Time } +func (c *SigningConfirmation) IsExpired() bool { + return c.ExpiresAt.Before(c.UpdatedAt) +} + type SigningProposalQuorum map[int]*SigningProposalParticipant type SigningParticipantStatus uint8 diff --git a/fsm/state_machines/signature_proposal_fsm/actions.go b/fsm/state_machines/signature_proposal_fsm/actions.go index 86f7e7b..2ff9be6 100644 --- a/fsm/state_machines/signature_proposal_fsm/actions.go +++ b/fsm/state_machines/signature_proposal_fsm/actions.go @@ -3,8 +3,6 @@ package signature_proposal_fsm import ( "errors" "fmt" - "time" - "github.com/depools/dc4bc/fsm/config" "github.com/depools/dc4bc/fsm/fsm" "github.com/depools/dc4bc/fsm/state_machines/internal" @@ -131,26 +129,24 @@ func (m *SignatureProposalFSM) actionProposalResponseByParticipant(inEvent fsm.E func (m *SignatureProposalFSM) actionValidateSignatureProposal(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { var ( - isContainsDeclined, isContainsExpired bool + isContainsDeclined bool ) m.payloadMu.Lock() defer m.payloadMu.Unlock() - tm := time.Now() + if m.payload.SignatureProposalPayload.IsExpired() { + outEvent = eventSetValidationCanceledByTimeout + return + } unconfirmedParticipants := m.payload.SigQuorumCount() + for _, participant := range m.payload.SignatureProposalPayload.Quorum { - if participant.Status == internal.SigConfirmationAwaitConfirmation { - if participant.UpdatedAt.Add(config.SignatureProposalConfirmationDeadline).Before(tm) { - isContainsExpired = true - } - } else { - if participant.Status == internal.SigConfirmationConfirmed { - unconfirmedParticipants-- - } else if participant.Status == internal.SigConfirmationDeclined { - isContainsDeclined = true - } + if participant.Status == internal.SigConfirmationConfirmed { + unconfirmedParticipants-- + } else if participant.Status == internal.SigConfirmationDeclined { + isContainsDeclined = true } } @@ -159,11 +155,6 @@ func (m *SignatureProposalFSM) actionValidateSignatureProposal(inEvent fsm.Event return } - if isContainsExpired { - outEvent = eventSetValidationCanceledByTimeout - return - } - // The are no declined and timed out participants, check for all confirmations if unconfirmedParticipants > 0 { return