Merge branch 'fsm-draft' into feat/airgapped-qr

This commit is contained in:
programmer10110 2020-08-19 19:26:18 +03:00
commit 0a42c75725
27 changed files with 795 additions and 404 deletions

View File

@ -138,7 +138,7 @@ func TestAirgappedAllSteps(t *testing.T) {
} }
entry := &responses.SignatureProposalParticipantStatusEntry{ entry := &responses.SignatureProposalParticipantStatusEntry{
ParticipantId: n.ParticipantID, ParticipantId: n.ParticipantID,
Title: n.Participant, Addr: n.Participant,
DkgPubKey: pubKey, DkgPubKey: pubKey,
} }
getCommitsRequest = append(getCommitsRequest, entry) getCommitsRequest = append(getCommitsRequest, entry)

View File

@ -68,7 +68,7 @@ func (am *AirgappedMachine) handleStateDkgCommitsAwaitConfirmations(o *client.Op
if err = pubKey.UnmarshalBinary(entry.DkgPubKey); err != nil { if err = pubKey.UnmarshalBinary(entry.DkgPubKey); err != nil {
return fmt.Errorf("failed to unmarshal pubkey: %w", err) return fmt.Errorf("failed to unmarshal pubkey: %w", err)
} }
dkgInstance.StorePubKey(entry.Title, entry.ParticipantId, pubKey) dkgInstance.StorePubKey(entry.Addr, entry.ParticipantId, pubKey)
} }
if err = dkgInstance.InitDKGInstance(); err != nil { if err = dkgInstance.InitDKGInstance(); err != nil {

View File

@ -2,20 +2,23 @@ package client
import ( import (
"context" "context"
"crypto/ed25519"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
dkgFSM "github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm"
"go.dedis.ch/kyber/v3"
"log" "log"
"path/filepath" "path/filepath"
"sync" "sync"
"time" "time"
fsmStateMachines "github.com/depools/dc4bc/fsm/state_machines" "github.com/depools/dc4bc/fsm/state_machines/signature_proposal_fsm"
"github.com/depools/dc4bc/fsm/state_machines"
"github.com/depools/dc4bc/fsm/fsm"
dkgFSM "github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm"
"github.com/depools/dc4bc/qr" "github.com/depools/dc4bc/qr"
"github.com/depools/dc4bc/storage" "github.com/depools/dc4bc/storage"
sign "go.dedis.ch/kyber/v3/sign/schnorr"
"go.dedis.ch/kyber/v3/util/key"
) )
const ( const (
@ -25,29 +28,35 @@ const (
type Client struct { type Client struct {
sync.Mutex sync.Mutex
userName string
address string
ctx context.Context ctx context.Context
fsm *fsmStateMachines.FSMInstance
state State state State
storage storage.Storage storage storage.Storage
keyStore KeyStore
qrProcessor qr.Processor qrProcessor qr.Processor
// these just a template
suite key.Suite
authKeyPair *key.Pair
} }
func NewClient( func NewClient(
ctx context.Context, ctx context.Context,
fsm *fsmStateMachines.FSMInstance, userName string,
state State, state State,
storage storage.Storage, storage storage.Storage,
keyStore KeyStore,
qrProcessor qr.Processor, qrProcessor qr.Processor,
) (*Client, error) { ) (*Client, error) {
keyPair, err := keyStore.LoadKeys(userName, "")
if err != nil {
return nil, fmt.Errorf("failed to LoadKeys: %w", err)
}
return &Client{ return &Client{
ctx: ctx, ctx: ctx,
fsm: fsm, userName: userName,
address: keyPair.GetAddr(),
state: state, state: state,
storage: storage, storage: storage,
keyStore: keyStore,
qrProcessor: qrProcessor, qrProcessor: qrProcessor,
}, nil }, nil
} }
@ -76,48 +85,8 @@ func (c *Client) Poll() error {
} }
for _, message := range messages { for _, message := range messages {
log.Println("Message:", message) if err := c.ProcessMessage(message); err != nil {
log.Println("Failed to process message:", err)
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.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)
} }
} }
case <-c.ctx.Done(): case <-c.ctx.Done():
@ -127,6 +96,65 @@ 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 := fsmInstance.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(message.DkgRoundID, fsmDump); err != nil {
return fmt.Errorf("failed to SaveFSM: %w", err)
}
return nil
}
func (c *Client) GetOperations() (map[string]*Operation, error) { func (c *Client) GetOperations() (map[string]*Operation, error) {
return c.state.GetOperations() return c.state.GetOperations()
} }
@ -188,16 +216,16 @@ func (c *Client) handleProcessedOperation(operation Operation) error {
return fmt.Errorf("processed operation does not match stored operation: %w", err) return fmt.Errorf("processed operation does not match stored operation: %w", err)
} }
sig, err := c.signMessage(operation.Result) message := storage.Message{
Event: string(operation.Type),
Data: operation.Result,
}
sig, err := c.signMessage(message.Bytes())
if err != nil { if err != nil {
return fmt.Errorf("failed to sign a message: %w", err) return fmt.Errorf("failed to sign a message: %w", err)
} }
message := storage.Message{ message.Signature = sig
To: operation.To,
Data: operation.Result,
Signature: sig,
CreatedAt: time.Now(),
}
if _, err := c.storage.Send(message); err != nil { if _, err := c.storage.Send(message); err != nil {
return fmt.Errorf("failed to post message: %w", err) return fmt.Errorf("failed to post message: %w", err)
@ -210,21 +238,49 @@ func (c *Client) handleProcessedOperation(operation Operation) error {
return nil return nil
} }
// it's just a template func (c *Client) getFSMInstance(dkgRoundID string) (*state_machines.FSMInstance, error) {
func (c *Client) signMessage(msg []byte) ([]byte, error) { var err error
//s, err := sign.Sign(c.suite, c.authKeyPair.Private, msg) fsmInstance, ok, err := c.state.LoadFSM(dkgRoundID)
//if err != nil { if err != nil {
// return nil, fmt.Errorf("failed to sign a message: %w", err) return nil, fmt.Errorf("failed to LoadFSM: %w", err)
//} }
return nil, nil
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
} }
// it's just a template func (c *Client) signMessage(message []byte) ([]byte, error) {
func (c *Client) verifyMessage(participant string, msg, signature []byte) error { keyPair, err := c.keyStore.LoadKeys(c.userName, "")
return sign.Verify(c.suite, c.getPublicKeyOfParticipant(participant), msg, signature) if err != nil {
return nil, fmt.Errorf("failed to LoadKeys: %w", err)
}
return ed25519.Sign(keyPair.Priv, message), nil
} }
// func should return public key of participant for checking his message signature func (c *Client) verifyMessage(fsmInstance *state_machines.FSMInstance, message storage.Message) error {
func (c *Client) getPublicKeyOfParticipant(participant string) kyber.Point { 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 return nil
} }

View File

@ -2,6 +2,7 @@ package client_test
import ( import (
"context" "context"
"crypto/ed25519"
"encoding/json" "encoding/json"
"errors" "errors"
"os" "os"
@ -9,16 +10,98 @@ import (
"testing" "testing"
"time" "time"
"github.com/depools/dc4bc/mocks/qrMocks"
"github.com/depools/dc4bc/client" "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/clientMocks"
"github.com/depools/dc4bc/mocks/qrMocks"
"github.com/depools/dc4bc/mocks/storageMocks" "github.com/depools/dc4bc/mocks/storageMocks"
"github.com/depools/dc4bc/storage"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/google/uuid"
"github.com/stretchr/testify/require" "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()
dkgRoundID := "dkg_round_id"
state := clientMocks.NewMockState(ctrl)
keyStore := clientMocks.NewMockKeyStore(ctrl)
stg := storageMocks.NewMockStorage(ctrl)
qrProcessor := qrMocks.NewMockProcessor(ctrl)
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",
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{
{
Addr: senderUserName,
PubKey: senderKeyPair.Pub,
DkgPubKey: make([]byte, 128),
},
{
Addr: "111",
PubKey: client.NewKeyPair().Pub,
DkgPubKey: make([]byte, 128),
},
{
Addr: "222",
PubKey: client.NewKeyPair().Pub,
DkgPubKey: make([]byte, 128),
},
{
Addr: "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(),
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(), gomock.Any()).Times(1).Return(nil)
err = clt.ProcessMessage(message)
req.NoError(err)
})
}
func TestClient_GetOperationsList(t *testing.T) { func TestClient_GetOperationsList(t *testing.T) {
var ( var (
ctx = context.Background() ctx = context.Background()
@ -28,10 +111,18 @@ func TestClient_GetOperationsList(t *testing.T) {
defer ctrl.Finish() defer ctrl.Finish()
state := clientMocks.NewMockState(ctrl) state := clientMocks.NewMockState(ctrl)
storage := storageMocks.NewMockStorage(ctrl) keyStore := clientMocks.NewMockKeyStore(ctrl)
stg := storageMocks.NewMockStorage(ctrl)
qrProcessor := qrMocks.NewMockProcessor(ctrl) qrProcessor := qrMocks.NewMockProcessor(ctrl)
clt, err := client.NewClient(ctx, nil, state, storage, qrProcessor) clt, err := client.NewClient(
ctx,
"test_client",
state,
stg,
keyStore,
qrProcessor,
)
req.NoError(err) req.NoError(err)
state.EXPECT().GetOperations().Times(1).Return(map[string]*client.Operation{}, nil) state.EXPECT().GetOperations().Times(1).Return(map[string]*client.Operation{}, nil)
@ -62,10 +153,18 @@ func TestClient_GetOperationQRPath(t *testing.T) {
defer ctrl.Finish() defer ctrl.Finish()
state := clientMocks.NewMockState(ctrl) state := clientMocks.NewMockState(ctrl)
storage := storageMocks.NewMockStorage(ctrl) keyStore := clientMocks.NewMockKeyStore(ctrl)
stg := storageMocks.NewMockStorage(ctrl)
qrProcessor := qrMocks.NewMockProcessor(ctrl) qrProcessor := qrMocks.NewMockProcessor(ctrl)
clt, err := client.NewClient(ctx, nil, state, storage, qrProcessor) clt, err := client.NewClient(
ctx,
"test_client",
state,
stg,
keyStore,
qrProcessor,
)
req.NoError(err) req.NoError(err)
operation := &client.Operation{ operation := &client.Operation{
@ -100,10 +199,18 @@ func TestClient_ReadProcessedOperation(t *testing.T) {
defer ctrl.Finish() defer ctrl.Finish()
state := clientMocks.NewMockState(ctrl) state := clientMocks.NewMockState(ctrl)
storage := storageMocks.NewMockStorage(ctrl) keyStore := clientMocks.NewMockKeyStore(ctrl)
stg := storageMocks.NewMockStorage(ctrl)
qrProcessor := qrMocks.NewMockProcessor(ctrl) qrProcessor := qrMocks.NewMockProcessor(ctrl)
clt, err := client.NewClient(ctx, nil, state, storage, qrProcessor) clt, err := client.NewClient(
ctx,
"test_client",
state,
stg,
keyStore,
qrProcessor,
)
req.NoError(err) req.NoError(err)
operation := &client.Operation{ operation := &client.Operation{
@ -126,7 +233,11 @@ func TestClient_ReadProcessedOperation(t *testing.T) {
qrProcessor.EXPECT().ReadQR().Return(processedOperationBz, nil).Times(1) qrProcessor.EXPECT().ReadQR().Return(processedOperationBz, nil).Times(1)
state.EXPECT().GetOperationByID(processedOperation.ID).Times(1).Return(operation, nil) state.EXPECT().GetOperationByID(processedOperation.ID).Times(1).Return(operation, nil)
state.EXPECT().DeleteOperation(processedOperation.ID).Times(1) 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() err = clt.ReadProcessedOperation()
req.NoError(err) req.NoError(err)
} }

97
client/keystore.go Normal file
View File

@ -0,0 +1,97 @@
package client
import (
"crypto/ed25519"
"encoding/hex"
"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,
}
}
func (p *KeyPair) GetAddr() string {
return hex.EncodeToString(p.Pub)
}

View File

@ -7,6 +7,8 @@ import (
"fmt" "fmt"
"sync" "sync"
"github.com/depools/dc4bc/fsm/state_machines"
"github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb"
) )
@ -20,8 +22,8 @@ type State interface {
SaveOffset(uint64) error SaveOffset(uint64) error
LoadOffset() (uint64, error) LoadOffset() (uint64, error)
SaveFSM([]byte) error SaveFSM(dkgRoundID string, dump []byte) error
LoadFSM() ([]byte, error) LoadFSM(dkgRoundID string) (*state_machines.FSMInstance, bool, error)
PutOperation(operation *Operation) error PutOperation(operation *Operation) error
DeleteOperation(operationID string) error DeleteOperation(operationID string) error
@ -45,15 +47,25 @@ func NewLevelDBState(stateDbPath string) (State, error) {
} }
// Init state key for operations JSON. // Init state key for operations JSON.
if err := state.initJsonKey(operationsKey, map[string]*Operation{}); err != nil { if _, err := state.stateDb.Get([]byte(operationsKey), nil); err != nil {
return nil, fmt.Errorf("failed to init %s storage: %w", operationsKey, err) 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. // Init state key for offset bytes.
bz := make([]byte, 8) if _, err := state.stateDb.Get([]byte(offsetKey), nil); err != nil {
binary.LittleEndian.PutUint64(bz, 0) bz := make([]byte, 8)
if err := db.Put([]byte(offsetKey), bz, nil); err != nil { binary.LittleEndian.PutUint64(bz, 0)
return nil, fmt.Errorf("failed to init %s storage: %w", offsetKey, err) 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 return state, nil
@ -95,17 +107,48 @@ func (s *LevelDBState) LoadOffset() (uint64, error) {
return offset, nil return offset, nil
} }
// TODO: implement. func (s *LevelDBState) SaveFSM(dkgRoundID string, dump []byte) error {
func (s *LevelDBState) SaveFSM(fsmState []byte) error { bz, err := s.stateDb.Get([]byte(fsmStateKey), nil)
if err := s.stateDb.Put([]byte(fsmStateKey), fsmState, nil); err != 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 fmt.Errorf("failed to save fsm state: %w", err)
} }
return nil return nil
} }
// TODO: implement. func (s *LevelDBState) LoadFSM(dkgRoundID string) (*state_machines.FSMInstance, bool, error) {
func (s *LevelDBState) LoadFSM() ([]byte, error) { bz, err := s.stateDb.Get([]byte(fsmStateKey), nil)
return 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 { func (s *LevelDBState) PutOperation(operation *Operation) error {

View File

@ -4,8 +4,13 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/depools/dc4bc/fsm/fsm"
"time" "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 type OperationType string
@ -41,26 +46,42 @@ func (o *Operation) Check(o2 *Operation) error {
return nil return nil
} }
type FSMRequest struct { func FSMRequestFromMessage(message storage.Message) (interface{}, error) {
Event fsm.Event var resolvedValue interface{}
Args []interface{} switch fsm.Event(message.Event) {
} case signature_proposal_fsm.EventInitProposal:
var req requests.SignatureProposalParticipantsListRequest
func FSMRequestFromBytes(data []byte) (FSMRequest, error) { if err := json.Unmarshal(message.Data, &req); err != nil {
var ( return fmt.Errorf("failed to unmarshal fsm req: %v", err), nil
r FSMRequest }
err error resolvedValue = req
) case dkg_proposal_fsm.EventDKGCommitConfirmationReceived:
if err = json.Unmarshal(data, &r); err != nil { var req requests.DKGProposalCommitConfirmationRequest
return r, err 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
}
func FSMRequestToBytes(event fsm.Event, req interface{}) ([]byte, error) { return resolvedValue, nil
fsmReq := FSMRequest{
Event: event,
Args: []interface{}{req},
}
return json.Marshal(fsmReq)
} }

View File

@ -15,21 +15,30 @@ import (
) )
const ( const (
flagUserName = "username"
flagListenAddr = "listen_addr" flagListenAddr = "listen_addr"
flagStateDBDSN = "state_dbdsn" flagStateDBDSN = "state_dbdsn"
flagStorageDBDSN = "storage_dbdsn" flagStorageDBDSN = "storage_dbdsn"
flagStoreDBDSN = "key_store_dbdsn"
) )
func init() { 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(flagStateDBDSN, "./dc4bc_client_state", "State DBDSN")
rootCmd.PersistentFlags().String(flagStorageDBDSN, "./dc4bc_file_storage", "Storage DBDSN") rootCmd.PersistentFlags().String(flagStorageDBDSN, "./dc4bc_file_storage", "Storage DBDSN")
rootCmd.PersistentFlags().String(flagStoreDBDSN, "./dc4bc_jey_store", "Key Store DBDSN")
} }
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "dc4bc_client", Use: "dc4bc_client",
Short: "dc4bc client implementation", Short: "dc4bc client implementation",
Run: func(cmd *cobra.Command, args []string) { 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) listenAddr, err := cmd.PersistentFlags().GetString(flagListenAddr)
if err != nil { if err != nil {
log.Fatalf("failed to read configuration: %v", err) log.Fatalf("failed to read configuration: %v", err)
@ -45,6 +54,11 @@ var rootCmd = &cobra.Command{
log.Fatalf("failed to read configuration: %v", err) 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 := context.Background()
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
@ -58,11 +72,16 @@ var rootCmd = &cobra.Command{
log.Fatalf("Failed to init storage client: %v", err) 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() processor := qr.NewCameraProcessor()
// TODO: create state machine. // 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 { if err != nil {
log.Fatalf("Failed to init client: %v", err) log.Fatalf("Failed to init client: %v", err)
} }

View File

@ -8,27 +8,43 @@ import (
"github.com/depools/dc4bc/fsm/state_machines/internal" "github.com/depools/dc4bc/fsm/state_machines/internal"
"github.com/depools/dc4bc/fsm/types/requests" "github.com/depools/dc4bc/fsm/types/requests"
"reflect" "reflect"
"time"
) )
// Init // Init
func (m *DKGProposalFSM) actionInitDKGProposal(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { 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 { if m.payload.DKGProposalPayload != nil {
return return
} }
m.payload.DKGProposalPayload = &internal.DKGConfirmation{ if len(args) != 1 {
Quorum: make(internal.DKGProposalQuorum), err = errors.New("{arg0} required {DefaultRequest}")
return
} }
for _, participant := range m.payload.SignatureProposalPayload.Quorum { request, ok := args[0].(requests.DefaultRequest)
m.payload.DKGProposalPayload.Quorum[participant.ParticipantId] = &internal.DKGProposalParticipant{
Title: participant.Title, 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, Status: internal.CommitAwaitConfirmation,
UpdatedAt: participant.UpdatedAt, 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? // Remove m.payload.SignatureProposalPayload?
@ -71,9 +87,11 @@ func (m *DKGProposalFSM) actionCommitConfirmationReceived(inEvent fsm.Event, arg
} }
copy(dkgProposalParticipant.Commit, request.Commit) copy(dkgProposalParticipant.Commit, request.Commit)
dkgProposalParticipant.UpdatedAt = request.CreatedAt
dkgProposalParticipant.Status = internal.CommitConfirmed dkgProposalParticipant.Status = internal.CommitConfirmed
dkgProposalParticipant.UpdatedAt = request.CreatedAt
m.payload.SignatureProposalPayload.UpdatedAt = request.CreatedAt
m.payload.DKGQuorumUpdate(request.ParticipantId, dkgProposalParticipant) m.payload.DKGQuorumUpdate(request.ParticipantId, dkgProposalParticipant)
return return
@ -81,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) { func (m *DKGProposalFSM) actionValidateDkgProposalAwaitCommits(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
var ( var (
isContainsError, isContainsExpired bool isContainsError bool
) )
m.payloadMu.Lock() m.payloadMu.Lock()
defer m.payloadMu.Unlock() defer m.payloadMu.Unlock()
tm := time.Now() if m.payload.DKGProposalPayload.IsExpired() {
outEvent = eventDKGCommitsConfirmationCancelByErrorInternal
return
}
unconfirmedParticipants := m.payload.DKGQuorumCount() unconfirmedParticipants := m.payload.DKGQuorumCount()
for _, participant := range m.payload.DKGProposalPayload.Quorum { for _, participant := range m.payload.DKGProposalPayload.Quorum {
if participant.Status == internal.CommitAwaitConfirmation { if participant.Status == internal.CommitConfirmationError {
if participant.UpdatedAt.Add(config.DkgConfirmationDeadline).Before(tm) { isContainsError = true
isContainsExpired = true } else if participant.Status == internal.CommitConfirmed {
} unconfirmedParticipants--
} else {
if participant.Status == internal.CommitConfirmationError {
isContainsError = true
} else if participant.Status == internal.CommitConfirmed {
unconfirmedParticipants--
}
} }
} }
@ -109,11 +124,6 @@ func (m *DKGProposalFSM) actionValidateDkgProposalAwaitCommits(inEvent fsm.Event
return return
} }
if isContainsExpired {
outEvent = eventDKGCommitsConfirmationCancelByErrorInternal
return
}
// The are no declined and timed out participants, check for all confirmations // The are no declined and timed out participants, check for all confirmations
if unconfirmedParticipants > 0 { if unconfirmedParticipants > 0 {
return return
@ -163,9 +173,11 @@ func (m *DKGProposalFSM) actionDealConfirmationReceived(inEvent fsm.Event, args
} }
copy(dkgProposalParticipant.Deal, request.Deal) copy(dkgProposalParticipant.Deal, request.Deal)
dkgProposalParticipant.UpdatedAt = request.CreatedAt
dkgProposalParticipant.Status = internal.DealConfirmed dkgProposalParticipant.Status = internal.DealConfirmed
dkgProposalParticipant.UpdatedAt = request.CreatedAt
m.payload.SignatureProposalPayload.UpdatedAt = request.CreatedAt
m.payload.DKGQuorumUpdate(request.ParticipantId, dkgProposalParticipant) m.payload.DKGQuorumUpdate(request.ParticipantId, dkgProposalParticipant)
return return
@ -173,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) { func (m *DKGProposalFSM) actionValidateDkgProposalAwaitDeals(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
var ( var (
isContainsError, isContainsExpired bool isContainsError bool
) )
m.payloadMu.Lock() m.payloadMu.Lock()
defer m.payloadMu.Unlock() defer m.payloadMu.Unlock()
tm := time.Now() if m.payload.DKGProposalPayload.IsExpired() {
outEvent = eventDKGDealsConfirmationCancelByTimeoutInternal
return
}
unconfirmedParticipants := m.payload.DKGQuorumCount() unconfirmedParticipants := m.payload.DKGQuorumCount()
for _, participant := range m.payload.DKGProposalPayload.Quorum { for _, participant := range m.payload.DKGProposalPayload.Quorum {
if participant.Status == internal.DealAwaitConfirmation { if participant.Status == internal.DealConfirmationError {
if participant.UpdatedAt.Add(config.DkgConfirmationDeadline).Before(tm) { isContainsError = true
isContainsExpired = true } else if participant.Status == internal.DealConfirmed {
} unconfirmedParticipants--
} else {
if participant.Status == internal.DealConfirmationError {
isContainsError = true
} else if participant.Status == internal.DealConfirmed {
unconfirmedParticipants--
}
} }
} }
@ -201,11 +210,6 @@ func (m *DKGProposalFSM) actionValidateDkgProposalAwaitDeals(inEvent fsm.Event,
return return
} }
if isContainsExpired {
outEvent = eventDKGDealsConfirmationCancelByTimeoutInternal
return
}
// The are no declined and timed out participants, check for all confirmations // The are no declined and timed out participants, check for all confirmations
if unconfirmedParticipants > 0 { if unconfirmedParticipants > 0 {
return return
@ -255,9 +259,11 @@ func (m *DKGProposalFSM) actionResponseConfirmationReceived(inEvent fsm.Event, a
} }
copy(dkgProposalParticipant.Response, request.Response) copy(dkgProposalParticipant.Response, request.Response)
dkgProposalParticipant.UpdatedAt = request.CreatedAt
dkgProposalParticipant.Status = internal.ResponseConfirmed dkgProposalParticipant.Status = internal.ResponseConfirmed
dkgProposalParticipant.UpdatedAt = request.CreatedAt
m.payload.SignatureProposalPayload.UpdatedAt = request.CreatedAt
m.payload.DKGQuorumUpdate(request.ParticipantId, dkgProposalParticipant) m.payload.DKGQuorumUpdate(request.ParticipantId, dkgProposalParticipant)
return return
@ -265,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) { func (m *DKGProposalFSM) actionValidateDkgProposalAwaitResponses(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
var ( var (
isContainsError, isContainsExpired bool isContainsError bool
) )
m.payloadMu.Lock() m.payloadMu.Lock()
defer m.payloadMu.Unlock() defer m.payloadMu.Unlock()
tm := time.Now() if m.payload.DKGProposalPayload.IsExpired() {
outEvent = eventDKGResponseConfirmationCancelByTimeoutInternal
return
}
unconfirmedParticipants := m.payload.DKGQuorumCount() unconfirmedParticipants := m.payload.DKGQuorumCount()
for _, participant := range m.payload.DKGProposalPayload.Quorum { for _, participant := range m.payload.DKGProposalPayload.Quorum {
if participant.Status == internal.ResponseAwaitConfirmation { if participant.Status == internal.ResponseConfirmationError {
if participant.UpdatedAt.Add(config.DkgConfirmationDeadline).Before(tm) { isContainsError = true
isContainsExpired = true } else if participant.Status == internal.ResponseConfirmed {
} unconfirmedParticipants--
} else {
if participant.Status == internal.ResponseConfirmationError {
isContainsError = true
} else if participant.Status == internal.ResponseConfirmed {
unconfirmedParticipants--
}
} }
} }
@ -293,11 +296,6 @@ func (m *DKGProposalFSM) actionValidateDkgProposalAwaitResponses(inEvent fsm.Eve
return return
} }
if isContainsExpired {
outEvent = eventDKGResponseConfirmationCancelByTimeoutInternal
return
}
// The are no declined and timed out participants, check for all confirmations // The are no declined and timed out participants, check for all confirmations
if unconfirmedParticipants > 0 { if unconfirmedParticipants > 0 {
return return
@ -347,9 +345,11 @@ func (m *DKGProposalFSM) actionMasterKeyConfirmationReceived(inEvent fsm.Event,
} }
copy(dkgProposalParticipant.MasterKey, request.MasterKey) copy(dkgProposalParticipant.MasterKey, request.MasterKey)
dkgProposalParticipant.UpdatedAt = request.CreatedAt
dkgProposalParticipant.Status = internal.MasterKeyConfirmed dkgProposalParticipant.Status = internal.MasterKeyConfirmed
dkgProposalParticipant.UpdatedAt = request.CreatedAt
m.payload.SignatureProposalPayload.UpdatedAt = request.CreatedAt
m.payload.DKGQuorumUpdate(request.ParticipantId, dkgProposalParticipant) m.payload.DKGQuorumUpdate(request.ParticipantId, dkgProposalParticipant)
return return
@ -357,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) { func (m *DKGProposalFSM) actionValidateDkgProposalAwaitMasterKey(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
var ( var (
isContainsError, isContainsExpired bool isContainsError bool
masterKeys [][]byte masterKeys [][]byte
) )
m.payloadMu.Lock() m.payloadMu.Lock()
defer m.payloadMu.Unlock() defer m.payloadMu.Unlock()
tm := time.Now() if m.payload.DKGProposalPayload.IsExpired() {
outEvent = eventDKGMasterKeyConfirmationCancelByTimeoutInternal
return
}
unconfirmedParticipants := m.payload.DKGQuorumCount() unconfirmedParticipants := m.payload.DKGQuorumCount()
for _, participant := range m.payload.DKGProposalPayload.Quorum { for _, participant := range m.payload.DKGProposalPayload.Quorum {
if participant.Status == internal.MasterKeyAwaitConfirmation { if participant.Status == internal.MasterKeyConfirmationError {
if participant.UpdatedAt.Add(config.DkgConfirmationDeadline).Before(tm) { isContainsError = true
isContainsExpired = true } else if participant.Status == internal.MasterKeyConfirmed {
} masterKeys = append(masterKeys, participant.MasterKey)
} else { unconfirmedParticipants--
if participant.Status == internal.MasterKeyConfirmationError {
isContainsError = true
} else if participant.Status == internal.MasterKeyConfirmed {
masterKeys = append(masterKeys, participant.MasterKey)
unconfirmedParticipants--
}
} }
} }
@ -388,11 +385,6 @@ func (m *DKGProposalFSM) actionValidateDkgProposalAwaitMasterKey(inEvent fsm.Eve
return return
} }
if isContainsExpired {
outEvent = eventDKGMasterKeyConfirmationCancelByTimeoutInternal
return
}
// Temporary simplest match master keys // Temporary simplest match master keys
if len(masterKeys) > 1 { if len(masterKeys) > 1 {
for i, masterKey := range masterKeys { for i, masterKey := range masterKeys {
@ -527,7 +519,9 @@ func (m *DKGProposalFSM) actionConfirmationError(inEvent fsm.Event, args ...inte
} }
dkgProposalParticipant.Error = request.Error dkgProposalParticipant.Error = request.Error
dkgProposalParticipant.UpdatedAt = request.CreatedAt dkgProposalParticipant.UpdatedAt = request.CreatedAt
m.payload.SignatureProposalPayload.UpdatedAt = request.CreatedAt
m.payload.DKGQuorumUpdate(request.ParticipantId, dkgProposalParticipant) m.payload.DKGQuorumUpdate(request.ParticipantId, dkgProposalParticipant)

View File

@ -1,6 +1,11 @@
package internal 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 { type DumpedMachineProvider interface {
fsm_pool.MachineProvider fsm_pool.MachineProvider
@ -10,10 +15,11 @@ type DumpedMachineProvider interface {
// DKG and other stages quorums are separated, // DKG and other stages quorums are separated,
// because unnecessary data may be unset // because unnecessary data may be unset
type DumpedMachineStatePayload struct { type DumpedMachineStatePayload struct {
TransactionId string DkgId string
SignatureProposalPayload *SignatureConfirmation SignatureProposalPayload *SignatureConfirmation
DKGProposalPayload *DKGConfirmation DKGProposalPayload *DKGConfirmation
SigningProposalPayload *SigningConfirmation SigningProposalPayload *SigningConfirmation
PubKeys map[string]ed25519.PublicKey
} }
// Signature quorum // Signature quorum
@ -26,7 +32,7 @@ func (p *DumpedMachineStatePayload) SigQuorumCount() int {
return count return count
} }
func (p *DumpedMachineStatePayload) SigQuorumExists(id string) bool { func (p *DumpedMachineStatePayload) SigQuorumExists(id int) bool {
var exists bool var exists bool
if p.SignatureProposalPayload.Quorum != nil { if p.SignatureProposalPayload.Quorum != nil {
_, exists = p.SignatureProposalPayload.Quorum[id] _, exists = p.SignatureProposalPayload.Quorum[id]
@ -34,14 +40,14 @@ func (p *DumpedMachineStatePayload) SigQuorumExists(id string) bool {
return exists return exists
} }
func (p *DumpedMachineStatePayload) SigQuorumGet(id string) (participant *SignatureProposalParticipant) { func (p *DumpedMachineStatePayload) SigQuorumGet(id int) (participant *SignatureProposalParticipant) {
if p.SignatureProposalPayload.Quorum != nil { if p.SignatureProposalPayload.Quorum != nil {
participant, _ = p.SignatureProposalPayload.Quorum[id] participant, _ = p.SignatureProposalPayload.Quorum[id]
} }
return return
} }
func (p *DumpedMachineStatePayload) SigQuorumUpdate(id string, participant *SignatureProposalParticipant) { func (p *DumpedMachineStatePayload) SigQuorumUpdate(id int, participant *SignatureProposalParticipant) {
if p.SignatureProposalPayload.Quorum != nil { if p.SignatureProposalPayload.Quorum != nil {
p.SignatureProposalPayload.Quorum[id] = participant p.SignatureProposalPayload.Quorum[id] = participant
} }
@ -111,3 +117,27 @@ func (p *DumpedMachineStatePayload) SigningQuorumUpdate(id int, participant *Sig
} }
return return
} }
func (p *DumpedMachineStatePayload) SetAddrHexPubKey(addr string, pubKey ed25519.PublicKey) {
if p.PubKeys == nil {
p.PubKeys = make(map[string]ed25519.PublicKey)
}
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 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
}

View File

@ -1,7 +1,7 @@
package internal package internal
import ( import (
"crypto/rsa" "crypto/ed25519"
"time" "time"
) )
@ -32,24 +32,27 @@ func (s ConfirmationParticipantStatus) String() string {
type SignatureConfirmation struct { type SignatureConfirmation struct {
Quorum SignatureProposalQuorum Quorum SignatureProposalQuorum
CreatedAt time.Time CreatedAt time.Time
UpdatedAt time.Time
ExpiresAt time.Time ExpiresAt time.Time
} }
type SignatureProposalParticipant struct { type SignatureProposalParticipant struct {
// Public title for address, such as name, nickname, organization Addr string
ParticipantId int PubKey ed25519.PublicKey
Title string DkgPubKey []byte
PubKey *rsa.PublicKey
DkgPubKey []byte
// For validation user confirmation: sign(InvitationSecret, PubKey) => user // For validation user confirmation: sign(InvitationSecret, PubKey) => user
InvitationSecret string InvitationSecret string
Status ConfirmationParticipantStatus Status ConfirmationParticipantStatus
UpdatedAt time.Time UpdatedAt time.Time
} }
func (c *SignatureConfirmation) IsExpired() bool {
return c.ExpiresAt.Before(c.UpdatedAt)
}
// Unique alias for map iteration - Public Key Fingerprint // Unique alias for map iteration - Public Key Fingerprint
// Excludes array merge and rotate operations // Excludes array merge and rotate operations
type SignatureProposalQuorum map[string]*SignatureProposalParticipant type SignatureProposalQuorum map[int]*SignatureProposalParticipant
// DKG proposal // DKG proposal
@ -71,8 +74,8 @@ const (
) )
type DKGProposalParticipant struct { type DKGProposalParticipant struct {
Title string Addr string
PubKey []byte DkgPubKey []byte
Commit []byte Commit []byte
Deal []byte Deal []byte
Response []byte Response []byte
@ -86,8 +89,13 @@ type DKGProposalQuorum map[int]*DKGProposalParticipant
type DKGConfirmation struct { type DKGConfirmation struct {
Quorum DKGProposalQuorum Quorum DKGProposalQuorum
CreatedAt *time.Time CreatedAt time.Time
ExpiresAt *time.Time UpdatedAt time.Time
ExpiresAt time.Time
}
func (c *DKGConfirmation) IsExpired() bool {
return c.ExpiresAt.Before(c.UpdatedAt)
} }
type DKGProposalParticipantStatus uint8 type DKGProposalParticipantStatus uint8
@ -131,9 +139,14 @@ type SigningConfirmation struct {
SrcPayload []byte SrcPayload []byte
EncryptedPayload []byte EncryptedPayload []byte
CreatedAt time.Time CreatedAt time.Time
UpdatedAt time.Time
ExpiresAt time.Time ExpiresAt time.Time
} }
func (c *SigningConfirmation) IsExpired() bool {
return c.ExpiresAt.Before(c.UpdatedAt)
}
type SigningProposalQuorum map[int]*SigningProposalParticipant type SigningProposalQuorum map[int]*SigningProposalParticipant
type SigningParticipantStatus uint8 type SigningParticipantStatus uint8
@ -171,7 +184,7 @@ func (s SigningParticipantStatus) String() string {
} }
type SigningProposalParticipant struct { type SigningProposalParticipant struct {
Title string Addr string
Status SigningParticipantStatus Status SigningParticipantStatus
PartialKey []byte PartialKey []byte
Error error Error error

View File

@ -1,13 +1,15 @@
package state_machines package state_machines
import ( import (
"crypto/ed25519"
"crypto/rand" "crypto/rand"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm"
"strings" "strings"
"github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm"
"github.com/depools/dc4bc/fsm/fsm" "github.com/depools/dc4bc/fsm/fsm"
"github.com/depools/dc4bc/fsm/fsm_pool" "github.com/depools/dc4bc/fsm/fsm_pool"
"github.com/depools/dc4bc/fsm/state_machines/internal" "github.com/depools/dc4bc/fsm/state_machines/internal"
@ -43,19 +45,13 @@ func init() {
// Create new fsm with unique id // Create new fsm with unique id
// transactionId required for unique identify dump // transactionId required for unique identify dump
func Create() (*FSMInstance, error) { func Create(dkgID string) (*FSMInstance, error) {
var ( var (
err error err error
i = &FSMInstance{} i = &FSMInstance{}
) )
transactionId, err := generateDkgTransactionId()
if err != nil {
return nil, err
}
err = i.InitDump(transactionId)
err = i.InitDump(dkgID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -90,6 +86,14 @@ func FromDump(data []byte) (*FSMInstance, error) {
return i, err return i, err
} }
func (i *FSMInstance) GetPubKeyByAddr(addr string) (ed25519.PublicKey, error) {
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) { func (i *FSMInstance) Do(event fsm.Event, args ...interface{}) (result *fsm.Response, dump []byte, err error) {
var dumpErr error var dumpErr error
@ -112,22 +116,22 @@ func (i *FSMInstance) Do(event fsm.Event, args ...interface{}) (result *fsm.Resp
return result, dump, err return result, dump, err
} }
func (i *FSMInstance) InitDump(transactionId string) error { func (i *FSMInstance) InitDump(dkgID string) error {
if i.dump != nil { if i.dump != nil {
return errors.New("dump already initialized") return errors.New("dump already initialized")
} }
transactionId = strings.TrimSpace(transactionId) dkgID = strings.TrimSpace(dkgID)
if transactionId == "" { if dkgID == "" {
return errors.New("empty transaction id") return errors.New("empty {dkgID}")
} }
i.dump = &FSMDump{ i.dump = &FSMDump{
TransactionId: transactionId, TransactionId: dkgID,
State: fsm.StateGlobalIdle, State: fsm.StateGlobalIdle,
Payload: &internal.DumpedMachineStatePayload{ Payload: &internal.DumpedMachineStatePayload{
TransactionId: transactionId, DkgId: dkgID,
SignatureProposalPayload: nil, SignatureProposalPayload: nil,
DKGProposalPayload: nil, DKGProposalPayload: nil,
}, },

View File

@ -3,22 +3,21 @@ package state_machines
import ( import (
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/sha1"
"crypto/x509" "crypto/x509"
"encoding/base64"
"fmt" "fmt"
"log"
"testing"
"time"
"github.com/depools/dc4bc/fsm/fsm" "github.com/depools/dc4bc/fsm/fsm"
dpf "github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm" dpf "github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm"
spf "github.com/depools/dc4bc/fsm/state_machines/signature_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/requests"
"github.com/depools/dc4bc/fsm/types/responses" "github.com/depools/dc4bc/fsm/types/responses"
"log"
"testing"
"time"
) )
type testExternalParticipants struct { type testExternalParticipants struct {
Title string Addr string
PrivKey *rsa.PrivateKey PrivKey *rsa.PrivateKey
PubKey *rsa.PublicKey PubKey *rsa.PublicKey
DkgPubKey []byte DkgPubKey []byte
@ -27,7 +26,9 @@ type testExternalParticipants struct {
var ( var (
tm = time.Now() tm = time.Now()
testParticipants = map[string]*testExternalParticipants{} dkgId = "1b7a6382afe0fbe2ff127a5779f5e9b042e685cabefeadcf4ef27c6089a56bfb"
testParticipants = map[int]*testExternalParticipants{}
testParticipantsListRequest = requests.SignatureProposalParticipantsListRequest{ testParticipantsListRequest = requests.SignatureProposalParticipantsListRequest{
Participants: []*requests.SignatureProposalParticipantsEntry{}, Participants: []*requests.SignatureProposalParticipantsEntry{},
@ -51,22 +52,17 @@ func init() {
key.Precompute() key.Precompute()
marshaledPubKey := x509.MarshalPKCS1PublicKey(&key.PublicKey)
hash := sha1.Sum(marshaledPubKey)
fingerprint := base64.StdEncoding.EncodeToString(hash[:])
pubKeyMock := make([]byte, 128) pubKeyMock := make([]byte, 128)
rand.Read(pubKeyMock) rand.Read(pubKeyMock)
participant := &testExternalParticipants{ participant := &testExternalParticipants{
Title: fmt.Sprintf("User %d", i), Addr: fmt.Sprintf("User %d", i),
PrivKey: key, PrivKey: key,
PubKey: &key.PublicKey, PubKey: &key.PublicKey,
DkgPubKey: pubKeyMock, DkgPubKey: pubKeyMock,
} }
testParticipants[fingerprint] = participant testParticipants[i] = participant
} }
participantsForRequest := make([]*requests.SignatureProposalParticipantsEntry, 0) participantsForRequest := make([]*requests.SignatureProposalParticipantsEntry, 0)
@ -74,7 +70,7 @@ func init() {
for _, participant := range testParticipants { for _, participant := range testParticipants {
participantsForRequest = append(participantsForRequest, &requests.SignatureProposalParticipantsEntry{ participantsForRequest = append(participantsForRequest, &requests.SignatureProposalParticipantsEntry{
Title: participant.Title, Addr: participant.Addr,
PubKey: x509.MarshalPKCS1PublicKey(participant.PubKey), PubKey: x509.MarshalPKCS1PublicKey(participant.PubKey),
DkgPubKey: participant.DkgPubKey, DkgPubKey: participant.DkgPubKey,
}) })
@ -84,7 +80,7 @@ func init() {
} }
func TestCreate_Positive(t *testing.T) { func TestCreate_Positive(t *testing.T) {
testFSMInstance, err := Create() testFSMInstance, err := Create(dkgId)
if err != nil { if err != nil {
t.Fatalf("expected nil error, got {%s}", err) t.Fatalf("expected nil error, got {%s}", err)
} }
@ -127,7 +123,7 @@ func compareState(t *testing.T, expected fsm.State, got fsm.State) {
// Test Workflow // Test Workflow
func Test_SignatureProposal_Init(t *testing.T) { func Test_SignatureProposal_Init(t *testing.T) {
testFSMInstance, err := Create() testFSMInstance, err := Create(dkgId)
compareErrNil(t, err) compareErrNil(t, err)
@ -186,16 +182,8 @@ func Test_SignatureProposal_Positive(t *testing.T) {
t.Fatalf("expected unique {ParticipantId}") t.Fatalf("expected unique {ParticipantId}")
} }
if participant.Title == "" { if participant.Addr == "" {
t.Fatalf("expected not empty {Title}") 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 participantsMap[participant.ParticipantId] = participant
@ -215,21 +203,9 @@ func Test_SignatureProposal_Positive(t *testing.T) {
compareFSMInstanceNotNil(t, testFSMInstance) 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{ fsmResponse, dump, err = testFSMInstance.Do(spf.EventConfirmSignatureProposal, requests.SignatureProposalParticipantRequest{
PubKeyFingerprint: participant.PubKeyFingerprint, ParticipantId: participant.ParticipantId,
DecryptedInvitation: string(encrypted), CreatedAt: tm,
CreatedAt: tm,
}) })
compareErrNil(t, err) compareErrNil(t, err)
@ -248,7 +224,9 @@ func Test_SignatureProposal_Positive(t *testing.T) {
testFSMInstance, err = FromDump(dump) 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) compareErrNil(t, err)
@ -268,7 +246,7 @@ func Test_SignatureProposal_Positive(t *testing.T) {
compareFSMInstanceNotNil(t, testFSMInstance) 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") t.Fatalf("not found external user data for response fingerprint")
} }
@ -304,7 +282,7 @@ func Test_SignatureProposal_Positive(t *testing.T) {
compareFSMInstanceNotNil(t, testFSMInstance) 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") t.Fatalf("not found external user data for response fingerprint")
} }
@ -340,7 +318,7 @@ func Test_SignatureProposal_Positive(t *testing.T) {
compareFSMInstanceNotNil(t, testFSMInstance) 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") t.Fatalf("not found external user data for response fingerprint")
} }
@ -382,7 +360,7 @@ func Test_SignatureProposal_Positive(t *testing.T) {
compareFSMInstanceNotNil(t, testFSMInstance) 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") t.Fatalf("not found external user data for response fingerprint")
} }

View File

@ -1,7 +1,6 @@
package signature_proposal_fsm package signature_proposal_fsm
import ( import (
"crypto/x509"
"errors" "errors"
"fmt" "fmt"
"github.com/depools/dc4bc/fsm/config" "github.com/depools/dc4bc/fsm/config"
@ -9,7 +8,6 @@ import (
"github.com/depools/dc4bc/fsm/state_machines/internal" "github.com/depools/dc4bc/fsm/state_machines/internal"
"github.com/depools/dc4bc/fsm/types/requests" "github.com/depools/dc4bc/fsm/types/requests"
"github.com/depools/dc4bc/fsm/types/responses" "github.com/depools/dc4bc/fsm/types/responses"
"time"
) )
// init -> awaitingConfirmations // init -> awaitingConfirmations
@ -35,31 +33,22 @@ func (m *SignatureProposalFSM) actionInitSignatureProposal(inEvent fsm.Event, ar
} }
m.payload.SignatureProposalPayload = &internal.SignatureConfirmation{ 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 { for index, participant := range request.Participants {
participantId := createFingerprint(&participant.PubKey) //participantId := createFingerprint(&participant.DkgPubKey)
secret, err := generateRandomString(32) m.payload.SignatureProposalPayload.Quorum[index] = &internal.SignatureProposalParticipant{
if err != nil { Addr: participant.Addr,
return inEvent, nil, errors.New("cannot generate source for {InvitationSecret}") PubKey: participant.PubKey,
DkgPubKey: participant.DkgPubKey,
Status: internal.SigConfirmationAwaitConfirmation,
UpdatedAt: request.CreatedAt,
} }
parsedPubKey, err := x509.ParsePKCS1PublicKey(participant.PubKey) m.payload.SetAddrHexPubKey(participant.Addr, participant.PubKey)
if err != nil {
return inEvent, nil, errors.New("cannot parse {PubKey}")
}
m.payload.SignatureProposalPayload.Quorum[participantId] = &internal.SignatureProposalParticipant{
ParticipantId: index,
Title: participant.Title,
PubKey: parsedPubKey,
DkgPubKey: participant.DkgPubKey,
InvitationSecret: secret,
Status: internal.SigConfirmationAwaitConfirmation,
UpdatedAt: request.CreatedAt,
}
} }
// Checking fo quorum length // Checking fo quorum length
@ -72,16 +61,10 @@ func (m *SignatureProposalFSM) actionInitSignatureProposal(inEvent fsm.Event, ar
responseData := make(responses.SignatureProposalParticipantInvitationsResponse, 0) responseData := make(responses.SignatureProposalParticipantInvitationsResponse, 0)
for pubKeyFingerprint, proposal := range m.payload.SignatureProposalPayload.Quorum { for participantId, 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{ responseEntry := &responses.SignatureProposalParticipantInvitationEntry{
ParticipantId: proposal.ParticipantId, ParticipantId: participantId,
Title: proposal.Title, Addr: proposal.Addr,
PubKeyFingerprint: pubKeyFingerprint,
EncryptedInvitation: encryptedInvitationSecret,
} }
responseData = append(responseData, responseEntry) responseData = append(responseData, responseEntry)
} }
@ -111,18 +94,12 @@ func (m *SignatureProposalFSM) actionProposalResponseByParticipant(inEvent fsm.E
return return
} }
if !m.payload.SigQuorumExists(request.PubKeyFingerprint) { if !m.payload.SigQuorumExists(request.ParticipantId) {
err = errors.New("{PubKeyFingerprint} not exist in quorum") err = errors.New("{ParticipantId} not exist in quorum")
return
}
signatureProposalParticipant := m.payload.SigQuorumGet(request.PubKeyFingerprint)
if signatureProposalParticipant.InvitationSecret != request.DecryptedInvitation {
err = errors.New("{InvitationSecret} not match {DecryptedInvitation}")
return return
} }
signatureProposalParticipant := m.payload.SigQuorumGet(request.ParticipantId)
if signatureProposalParticipant.UpdatedAt.Add(config.SignatureProposalConfirmationDeadline).Before(request.CreatedAt) { if signatureProposalParticipant.UpdatedAt.Add(config.SignatureProposalConfirmationDeadline).Before(request.CreatedAt) {
outEvent = eventSetValidationCanceledByTimeout outEvent = eventSetValidationCanceledByTimeout
return return
@ -145,33 +122,31 @@ func (m *SignatureProposalFSM) actionProposalResponseByParticipant(inEvent fsm.E
signatureProposalParticipant.UpdatedAt = request.CreatedAt signatureProposalParticipant.UpdatedAt = request.CreatedAt
m.payload.SigQuorumUpdate(request.PubKeyFingerprint, signatureProposalParticipant) m.payload.SigQuorumUpdate(request.ParticipantId, signatureProposalParticipant)
return return
} }
func (m *SignatureProposalFSM) actionValidateSignatureProposal(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { func (m *SignatureProposalFSM) actionValidateSignatureProposal(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
var ( var (
isContainsDeclined, isContainsExpired bool isContainsDeclined bool
) )
m.payloadMu.Lock() m.payloadMu.Lock()
defer m.payloadMu.Unlock() defer m.payloadMu.Unlock()
tm := time.Now() if m.payload.SignatureProposalPayload.IsExpired() {
outEvent = eventSetValidationCanceledByTimeout
return
}
unconfirmedParticipants := m.payload.SigQuorumCount() unconfirmedParticipants := m.payload.SigQuorumCount()
for _, participant := range m.payload.SignatureProposalPayload.Quorum { for _, participant := range m.payload.SignatureProposalPayload.Quorum {
if participant.Status == internal.SigConfirmationAwaitConfirmation { if participant.Status == internal.SigConfirmationConfirmed {
if participant.UpdatedAt.Add(config.SignatureProposalConfirmationDeadline).Before(tm) { unconfirmedParticipants--
isContainsExpired = true } else if participant.Status == internal.SigConfirmationDeclined {
} isContainsDeclined = true
} else {
if participant.Status == internal.SigConfirmationConfirmed {
unconfirmedParticipants--
} else if participant.Status == internal.SigConfirmationDeclined {
isContainsDeclined = true
}
} }
} }
@ -180,11 +155,6 @@ func (m *SignatureProposalFSM) actionValidateSignatureProposal(inEvent fsm.Event
return return
} }
if isContainsExpired {
outEvent = eventSetValidationCanceledByTimeout
return
}
// The are no declined and timed out participants, check for all confirmations // The are no declined and timed out participants, check for all confirmations
if unconfirmedParticipants > 0 { if unconfirmedParticipants > 0 {
return return
@ -192,11 +162,10 @@ func (m *SignatureProposalFSM) actionValidateSignatureProposal(inEvent fsm.Event
responseData := make(responses.SignatureProposalParticipantStatusResponse, 0) responseData := make(responses.SignatureProposalParticipantStatusResponse, 0)
for _, participant := range m.payload.SignatureProposalPayload.Quorum { for participantId, participant := range m.payload.SignatureProposalPayload.Quorum {
responseEntry := &responses.SignatureProposalParticipantStatusEntry{ responseEntry := &responses.SignatureProposalParticipantStatusEntry{
ParticipantId: participant.ParticipantId, ParticipantId: participantId,
Title: participant.Title, Addr: participant.Addr,
DkgPubKey: participant.DkgPubKey,
Status: uint8(participant.Status), Status: uint8(participant.Status),
} }
responseData = append(responseData, responseEntry) responseData = append(responseData, responseEntry)
@ -211,10 +180,10 @@ func (m *SignatureProposalFSM) actionSignatureProposalCanceledByTimeout(inEvent
responseData := make(responses.SignatureProposalParticipantStatusResponse, 0) responseData := make(responses.SignatureProposalParticipantStatusResponse, 0)
for _, participant := range m.payload.SignatureProposalPayload.Quorum { for participantId, participant := range m.payload.SignatureProposalPayload.Quorum {
responseEntry := &responses.SignatureProposalParticipantStatusEntry{ responseEntry := &responses.SignatureProposalParticipantStatusEntry{
ParticipantId: participant.ParticipantId, ParticipantId: participantId,
Title: participant.Title, Addr: participant.Addr,
Status: uint8(participant.Status), Status: uint8(participant.Status),
} }
responseData = append(responseData, responseEntry) responseData = append(responseData, responseEntry)

View File

@ -5,6 +5,7 @@ import (
"crypto/rsa" "crypto/rsa"
"crypto/sha1" "crypto/sha1"
"encoding/base64" "encoding/base64"
"github.com/depools/dc4bc/fsm/state_machines/internal" "github.com/depools/dc4bc/fsm/state_machines/internal"
"github.com/depools/dc4bc/fsm/types/responses" "github.com/depools/dc4bc/fsm/types/responses"
) )
@ -13,12 +14,9 @@ import (
func ProposalParticipantsQuorumToResponse(list *internal.SignatureProposalQuorum) responses.SignatureProposalParticipantInvitationsResponse { func ProposalParticipantsQuorumToResponse(list *internal.SignatureProposalQuorum) responses.SignatureProposalParticipantInvitationsResponse {
var response responses.SignatureProposalParticipantInvitationsResponse var response responses.SignatureProposalParticipantInvitationsResponse
for quorumId, parcipant := range *list { for _, participant := range *list {
response = append(response, &responses.SignatureProposalParticipantInvitationEntry{ response = append(response, &responses.SignatureProposalParticipantInvitationEntry{
Title: parcipant.Title, Addr: participant.Addr,
PubKeyFingerprint: quorumId,
// TODO: Add encryption
EncryptedInvitation: parcipant.InvitationSecret,
}) })
} }
return response return response

View File

@ -14,13 +14,31 @@ func (m *SigningProposalFSM) actionInitSigningProposal(inEvent fsm.Event, args .
m.payloadMu.Lock() m.payloadMu.Lock()
defer m.payloadMu.Unlock() defer m.payloadMu.Unlock()
m.payload.SigningProposalPayload = &internal.SigningConfirmation{ if len(args) != 1 {
Quorum: make(internal.SigningProposalQuorum), err = errors.New("{arg0} required {DefaultRequest}")
return
} }
for _, participant := range m.payload.SignatureProposalPayload.Quorum { request, ok := args[0].(requests.DefaultRequest)
m.payload.SigningProposalPayload.Quorum[participant.ParticipantId] = &internal.SigningProposalParticipant{
Title: participant.Title, 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, Status: internal.SigningIdle,
UpdatedAt: participant.UpdatedAt, UpdatedAt: participant.UpdatedAt,
} }
@ -49,6 +67,8 @@ func (m *SigningProposalFSM) actionStartSigningProposal(inEvent fsm.Event, args
return return
} }
m.payload.SigningProposalPayload.CreatedAt = request.CreatedAt
return return
} }

View File

@ -23,10 +23,14 @@ const (
StateSigningAwaitPartialKeys = fsm.State("state_signing_await_partial_keys") StateSigningAwaitPartialKeys = fsm.State("state_signing_await_partial_keys")
// Cancelled // Cancelled
StateSigningPartialKeysAwaitCancelledByTimeout = fsm.State("state_signing_partial_keys_await_cancelled_by_timeout") StateSigningPartialKeysAwaitCancelledByTimeout = fsm.State("state_signing_partial_signatures_await_cancelled_by_timeout")
StateSigningPartialKeysAwaitCancelledByParticipant = fsm.State("state_signing_partial_keys_await_cancelled_by_participant") 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 // Events

View File

@ -0,0 +1,7 @@
package requests
import "time"
type DefaultRequest struct {
CreatedAt time.Time
}

View File

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

View File

@ -14,7 +14,7 @@ type SignatureProposalParticipantsListRequest struct {
type SignatureProposalParticipantsEntry struct { type SignatureProposalParticipantsEntry struct {
// Public title for address, such as name, nickname, organization // Public title for address, such as name, nickname, organization
Title string Addr string
PubKey []byte PubKey []byte
DkgPubKey []byte DkgPubKey []byte
} }
@ -24,7 +24,6 @@ type SignatureProposalParticipantsEntry struct {
// "event_sig_proposal_decline_by_participant" // "event_sig_proposal_decline_by_participant"
type SignatureProposalParticipantRequest struct { type SignatureProposalParticipantRequest struct {
// Key for link invitations to participants // Key for link invitations to participants
PubKeyFingerprint string ParticipantId int
DecryptedInvitation string CreatedAt time.Time
CreatedAt time.Time
} }

View File

@ -3,6 +3,7 @@ package requests
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/depools/dc4bc/fsm/config" "github.com/depools/dc4bc/fsm/config"
) )
@ -20,12 +21,12 @@ func (r *SignatureProposalParticipantsListRequest) Validate() error {
} }
for _, participant := range r.Participants { for _, participant := range r.Participants {
if len(participant.Title) < 3 { if len(participant.Addr) < 3 {
return errors.New("{Title} minimum length is {3}") return errors.New("{Addr} minimum length is {3}")
} }
if len(participant.Title) > 150 { if len(participant.Addr) > 150 {
return errors.New("{Title} maximum length is {150}") return errors.New("{Addr} maximum length is {150}")
} }
if len(participant.PubKey) < 10 { if len(participant.PubKey) < 10 {
@ -45,12 +46,8 @@ func (r *SignatureProposalParticipantsListRequest) Validate() error {
} }
func (r *SignatureProposalParticipantRequest) Validate() error { func (r *SignatureProposalParticipantRequest) Validate() error {
if len(r.PubKeyFingerprint) == 0 { if r.ParticipantId < 0 {
return errors.New("{PubKeyFingerprint} cannot zero length") return errors.New("{ParticipantId} cannot be a negative number")
}
if len(r.DecryptedInvitation) == 0 {
return errors.New("{DecryptedInvitation} cannot zero length")
} }
if r.CreatedAt.IsZero() { if r.CreatedAt.IsZero() {

View File

@ -10,11 +10,7 @@ type SignatureProposalParticipantInvitationsResponse []*SignatureProposalPartici
type SignatureProposalParticipantInvitationEntry struct { type SignatureProposalParticipantInvitationEntry struct {
ParticipantId int ParticipantId int
// Public title for address, such as name, nickname, organization // Public title for address, such as name, nickname, organization
Title string Addr string
// Key for link invitations to participants
PubKeyFingerprint string
// Encrypted with public key secret
EncryptedInvitation string
} }
// Public lists for proposal confirmation process // Public lists for proposal confirmation process
@ -23,7 +19,7 @@ type SignatureProposalParticipantStatusResponse []*SignatureProposalParticipantS
type SignatureProposalParticipantStatusEntry struct { type SignatureProposalParticipantStatusEntry struct {
ParticipantId int ParticipantId int
Title string Addr string
DkgPubKey []byte
Status uint8 Status uint8
DkgPubKey []byte
} }

View File

@ -29,6 +29,7 @@ func main() {
var numNodes = 4 var numNodes = 4
for nodeID := 0; nodeID < numNodes; nodeID++ { for nodeID := 0; nodeID < numNodes; nodeID++ {
var ctx = context.Background() var ctx = context.Background()
var userName = fmt.Sprintf("node_%d", nodeID)
var state, err = client.NewLevelDBState(fmt.Sprintf("/tmp/dc4bc_node_%d_state", nodeID)) var state, err = client.NewLevelDBState(fmt.Sprintf("/tmp/dc4bc_node_%d_state", nodeID))
if err != nil { if err != nil {
log.Fatalf("node %d failed to init state: %v\n", nodeID, err) 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) 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( clt, err := client.NewClient(
ctx, ctx,
userName,
nil, nil,
state, state,
stg, stg,
keyStore,
qr.NewCameraProcessor(), qr.NewCameraProcessor(),
) )
if err != nil { if err != nil {

View File

@ -5,10 +5,10 @@
package clientMocks package clientMocks
import ( import (
reflect "reflect"
client "github.com/depools/dc4bc/client" client "github.com/depools/dc4bc/client"
state_machines "github.com/depools/dc4bc/fsm/state_machines"
gomock "github.com/golang/mock/gomock" gomock "github.com/golang/mock/gomock"
reflect "reflect"
) )
// MockState is a mock of State interface // MockState is a mock of State interface
@ -64,32 +64,33 @@ func (mr *MockStateMockRecorder) LoadOffset() *gomock.Call {
} }
// SaveFSM mocks base method // SaveFSM mocks base method
func (m *MockState) SaveFSM(arg0 []byte) error { func (m *MockState) SaveFSM(dkgRoundID string, dump []byte) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SaveFSM", arg0) ret := m.ctrl.Call(m, "SaveFSM", dkgRoundID, dump)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// SaveFSM indicates an expected call of SaveFSM // SaveFSM indicates an expected call of SaveFSM
func (mr *MockStateMockRecorder) SaveFSM(arg0 []byte) *gomock.Call { func (mr *MockStateMockRecorder) SaveFSM(dkgRoundID, dump interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() 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 // 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() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "LoadFSM") ret := m.ctrl.Call(m, "LoadFSM", dkgRoundID)
ret0, _ := ret[0].([]byte) ret0, _ := ret[0].(*state_machines.FSMInstance)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(bool)
return ret0, ret1 ret2, _ := ret[2].(error)
return ret0, ret1, ret2
} }
// LoadFSM indicates an expected call of LoadFSM // 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() 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 // PutOperation mocks base method

View File

@ -1,5 +1,6 @@
package mocks package mocks
//go:generate mockgen -source=./../client/state.go -destination=./clientMocks/state_mock.go -package=clientMocks //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=./../storage/types.go -destination=./storageMocks/storage_mock.go -package=storageMocks
//go:generate mockgen -source=./../qr/qr.go -destination=./qrMocks/qr_mock.go -package=qrMocks //go:generate mockgen -source=./../qr/qr.go -destination=./qrMocks/qr_mock.go -package=qrMocks

View File

@ -5,10 +5,9 @@
package storageMocks package storageMocks
import ( import (
reflect "reflect"
storage "github.com/depools/dc4bc/storage" storage "github.com/depools/dc4bc/storage"
gomock "github.com/golang/mock/gomock" gomock "github.com/golang/mock/gomock"
reflect "reflect"
) )
// MockStorage is a mock of Storage interface // MockStorage is a mock of Storage interface

View File

@ -1,18 +1,33 @@
package storage package storage
import "time" import (
"bytes"
"crypto/ed25519"
)
type Message struct { type Message struct {
To string `json:"to"` ID string `json:"id"`
Data []byte `json:"data"` DkgRoundID string `json:"dkg_round_id"`
Signature []byte `json:"signature"` Offset uint64 `json:"offset"`
ID string `json:"id"` Event string `json:"event"`
Offset uint64 `json:"offset"` Data []byte `json:"data"`
CreatedAt time.Time `json:"created_at"` Signature []byte `json:"signature"`
SenderAddr string `json:"sender"`
}
func (m *Message) Bytes() []byte {
buf := bytes.NewBuffer(nil)
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 { type Storage interface {
Send(message Message) (Message, error) Send(message Message) (Message, error)
GetMessages(offset uint64) ([]Message, error) GetMessages(offset uint64) ([]Message, error)
Close() error Close() error
} }