This commit is contained in:
Andrej Zavgorodnij 2020-08-18 19:41:43 +03:00
parent db8763ffff
commit 8c398091cd
12 changed files with 176 additions and 76 deletions

View File

@ -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
}

View File

@ -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,

View File

@ -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)
}

View File

@ -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 {

View File

@ -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

View File

@ -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 == "" {

View File

@ -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,

View File

@ -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

View File

@ -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
}

View File

@ -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 {

View File

@ -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

View File

@ -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()