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{
ParticipantId: n.ParticipantID,
Title: n.Participant,
Addr: n.Participant,
DkgPubKey: pubKey,
}
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 {
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 {

View File

@ -2,20 +2,23 @@ package client
import (
"context"
"crypto/ed25519"
"encoding/json"
"errors"
"fmt"
dkgFSM "github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm"
"go.dedis.ch/kyber/v3"
"log"
"path/filepath"
"sync"
"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/storage"
sign "go.dedis.ch/kyber/v3/sign/schnorr"
"go.dedis.ch/kyber/v3/util/key"
)
const (
@ -25,29 +28,35 @@ const (
type Client struct {
sync.Mutex
userName string
address string
ctx context.Context
fsm *fsmStateMachines.FSMInstance
state State
storage storage.Storage
keyStore KeyStore
qrProcessor qr.Processor
// these just a template
suite key.Suite
authKeyPair *key.Pair
}
func NewClient(
ctx context.Context,
fsm *fsmStateMachines.FSMInstance,
userName string,
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,
fsm: fsm,
userName: userName,
address: keyPair.GetAddr(),
state: state,
storage: storage,
keyStore: keyStore,
qrProcessor: qrProcessor,
}, nil
}
@ -76,48 +85,8 @@ func (c *Client) Poll() error {
}
for _, message := range messages {
log.Println("Message:", message)
fsmReq, err := FSMRequestFromBytes(message.Data)
if err != nil {
return fmt.Errorf("failed to get FSMRequest from message data: %w", err)
}
resp, fsmDump, err := c.fsm.Do(fsmReq.Event, fsmReq.Args...)
if err != nil {
return fmt.Errorf("failed to Do operation in FSM: %w", err)
}
var operation *Operation
switch resp.State {
// if the new state is waiting for RPC to airgapped machine
case dkgFSM.StateDkgCommitsAwaitConfirmations, dkgFSM.StateDkgDealsAwaitConfirmations,
dkgFSM.StateDkgResponsesAwaitConfirmations:
bz, err := json.Marshal(resp.Data)
if err != nil {
return fmt.Errorf("failed to marshal FSM response: %w", err)
}
operation = &Operation{
Type: OperationType(resp.State),
Payload: bz,
}
default:
log.Printf("State %s does not require an operation", resp.State)
}
if operation != nil {
if err := c.state.PutOperation(operation); err != nil {
return fmt.Errorf("failed to PutOperation: %w", err)
}
}
if err := c.state.SaveOffset(message.Offset); err != nil {
return fmt.Errorf("failed to SaveOffset: %w", err)
}
if err := c.state.SaveFSM(fsmDump); err != nil {
return fmt.Errorf("failed to SaveFSM: %w", err)
if err := c.ProcessMessage(message); err != nil {
log.Println("Failed to process message:", err)
}
}
case <-c.ctx.Done():
@ -127,6 +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) {
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)
}
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 {
return fmt.Errorf("failed to sign a message: %w", err)
}
message := storage.Message{
To: operation.To,
Data: operation.Result,
Signature: sig,
CreatedAt: time.Now(),
}
message.Signature = sig
if _, err := c.storage.Send(message); err != nil {
return fmt.Errorf("failed to post message: %w", err)
@ -210,21 +238,49 @@ func (c *Client) handleProcessedOperation(operation Operation) error {
return nil
}
// it's just a template
func (c *Client) signMessage(msg []byte) ([]byte, error) {
//s, err := sign.Sign(c.suite, c.authKeyPair.Private, msg)
//if err != nil {
// return nil, fmt.Errorf("failed to sign a message: %w", err)
//}
return nil, nil
func (c *Client) 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
}
// it's just a template
func (c *Client) verifyMessage(participant string, msg, signature []byte) error {
return sign.Verify(c.suite, c.getPublicKeyOfParticipant(participant), msg, signature)
func (c *Client) signMessage(message []byte) ([]byte, error) {
keyPair, err := c.keyStore.LoadKeys(c.userName, "")
if err != nil {
return nil, fmt.Errorf("failed to LoadKeys: %w", err)
}
return ed25519.Sign(keyPair.Priv, message), nil
}
// func should return public key of participant for checking his message signature
func (c *Client) getPublicKeyOfParticipant(participant string) kyber.Point {
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

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

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"
"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
@ -45,15 +47,25 @@ func NewLevelDBState(stateDbPath string) (State, error) {
}
// Init state key for operations JSON.
if err := state.initJsonKey(operationsKey, map[string]*Operation{}); err != nil {
return nil, fmt.Errorf("failed to init %s storage: %w", operationsKey, err)
if _, err := state.stateDb.Get([]byte(operationsKey), nil); err != nil {
if err := state.initJsonKey(operationsKey, map[string]*Operation{}); err != nil {
return nil, fmt.Errorf("failed to init %s storage: %w", operationsKey, err)
}
}
// Init state key for offset bytes.
bz := make([]byte, 8)
binary.LittleEndian.PutUint64(bz, 0)
if err := db.Put([]byte(offsetKey), bz, nil); err != nil {
return nil, fmt.Errorf("failed to init %s storage: %w", offsetKey, err)
if _, err := state.stateDb.Get([]byte(offsetKey), nil); err != nil {
bz := make([]byte, 8)
binary.LittleEndian.PutUint64(bz, 0)
if err := db.Put([]byte(offsetKey), bz, nil); err != nil {
return nil, fmt.Errorf("failed to init %s storage: %w", offsetKey, err)
}
}
if _, err := state.stateDb.Get([]byte(fsmStateKey), nil); err != nil {
if err := db.Put([]byte(fsmStateKey), []byte{}, nil); err != nil {
return nil, fmt.Errorf("failed to init %s storage: %w", offsetKey, err)
}
}
return state, nil
@ -95,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

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

View File

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

View File

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

View File

@ -1,6 +1,11 @@
package internal
import "github.com/depools/dc4bc/fsm/fsm_pool"
import (
"crypto/ed25519"
"encoding/hex"
"errors"
"github.com/depools/dc4bc/fsm/fsm_pool"
)
type DumpedMachineProvider interface {
fsm_pool.MachineProvider
@ -10,10 +15,11 @@ type DumpedMachineProvider interface {
// DKG and other stages quorums are separated,
// because unnecessary data may be unset
type DumpedMachineStatePayload struct {
TransactionId string
DkgId string
SignatureProposalPayload *SignatureConfirmation
DKGProposalPayload *DKGConfirmation
SigningProposalPayload *SigningConfirmation
PubKeys map[string]ed25519.PublicKey
}
// Signature quorum
@ -26,7 +32,7 @@ func (p *DumpedMachineStatePayload) SigQuorumCount() int {
return count
}
func (p *DumpedMachineStatePayload) SigQuorumExists(id string) bool {
func (p *DumpedMachineStatePayload) SigQuorumExists(id int) bool {
var exists bool
if p.SignatureProposalPayload.Quorum != nil {
_, exists = p.SignatureProposalPayload.Quorum[id]
@ -34,14 +40,14 @@ func (p *DumpedMachineStatePayload) SigQuorumExists(id string) bool {
return exists
}
func (p *DumpedMachineStatePayload) SigQuorumGet(id string) (participant *SignatureProposalParticipant) {
func (p *DumpedMachineStatePayload) SigQuorumGet(id int) (participant *SignatureProposalParticipant) {
if p.SignatureProposalPayload.Quorum != nil {
participant, _ = p.SignatureProposalPayload.Quorum[id]
}
return
}
func (p *DumpedMachineStatePayload) SigQuorumUpdate(id string, participant *SignatureProposalParticipant) {
func (p *DumpedMachineStatePayload) SigQuorumUpdate(id int, participant *SignatureProposalParticipant) {
if p.SignatureProposalPayload.Quorum != nil {
p.SignatureProposalPayload.Quorum[id] = participant
}
@ -111,3 +117,27 @@ func (p *DumpedMachineStatePayload) SigningQuorumUpdate(id int, participant *Sig
}
return
}
func (p *DumpedMachineStatePayload) SetAddrHexPubKey(addr string, 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
import (
"crypto/rsa"
"crypto/ed25519"
"time"
)
@ -32,24 +32,27 @@ func (s ConfirmationParticipantStatus) String() string {
type SignatureConfirmation struct {
Quorum SignatureProposalQuorum
CreatedAt time.Time
UpdatedAt time.Time
ExpiresAt time.Time
}
type SignatureProposalParticipant struct {
// Public title for address, such as name, nickname, organization
ParticipantId int
Title string
PubKey *rsa.PublicKey
DkgPubKey []byte
Addr string
PubKey ed25519.PublicKey
DkgPubKey []byte
// For validation user confirmation: sign(InvitationSecret, PubKey) => user
InvitationSecret string
Status ConfirmationParticipantStatus
UpdatedAt time.Time
}
func (c *SignatureConfirmation) IsExpired() bool {
return c.ExpiresAt.Before(c.UpdatedAt)
}
// Unique alias for map iteration - Public Key Fingerprint
// Excludes array merge and rotate operations
type SignatureProposalQuorum map[string]*SignatureProposalParticipant
type SignatureProposalQuorum map[int]*SignatureProposalParticipant
// DKG proposal
@ -71,8 +74,8 @@ const (
)
type DKGProposalParticipant struct {
Title string
PubKey []byte
Addr string
DkgPubKey []byte
Commit []byte
Deal []byte
Response []byte
@ -86,8 +89,13 @@ type DKGProposalQuorum map[int]*DKGProposalParticipant
type DKGConfirmation struct {
Quorum DKGProposalQuorum
CreatedAt *time.Time
ExpiresAt *time.Time
CreatedAt time.Time
UpdatedAt time.Time
ExpiresAt time.Time
}
func (c *DKGConfirmation) IsExpired() bool {
return c.ExpiresAt.Before(c.UpdatedAt)
}
type DKGProposalParticipantStatus uint8
@ -131,9 +139,14 @@ type SigningConfirmation struct {
SrcPayload []byte
EncryptedPayload []byte
CreatedAt time.Time
UpdatedAt time.Time
ExpiresAt time.Time
}
func (c *SigningConfirmation) IsExpired() bool {
return c.ExpiresAt.Before(c.UpdatedAt)
}
type SigningProposalQuorum map[int]*SigningProposalParticipant
type SigningParticipantStatus uint8
@ -171,7 +184,7 @@ func (s SigningParticipantStatus) String() string {
}
type SigningProposalParticipant struct {
Title string
Addr string
Status SigningParticipantStatus
PartialKey []byte
Error 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,14 @@ func FromDump(data []byte) (*FSMInstance, error) {
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) {
var dumpErr error
@ -112,22 +116,22 @@ func (i *FSMInstance) Do(event fsm.Event, args ...interface{}) (result *fsm.Resp
return result, dump, err
}
func (i *FSMInstance) InitDump(transactionId string) error {
func (i *FSMInstance) InitDump(dkgID string) error {
if i.dump != nil {
return errors.New("dump already initialized")
}
transactionId = strings.TrimSpace(transactionId)
dkgID = strings.TrimSpace(dkgID)
if transactionId == "" {
return errors.New("empty transaction id")
if dkgID == "" {
return errors.New("empty {dkgID}")
}
i.dump = &FSMDump{
TransactionId: transactionId,
TransactionId: dkgID,
State: fsm.StateGlobalIdle,
Payload: &internal.DumpedMachineStatePayload{
TransactionId: transactionId,
DkgId: dkgID,
SignatureProposalPayload: nil,
DKGProposalPayload: nil,
},

View File

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

View File

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

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,9 @@ import (
func ProposalParticipantsQuorumToResponse(list *internal.SignatureProposalQuorum) responses.SignatureProposalParticipantInvitationsResponse {
var response responses.SignatureProposalParticipantInvitationsResponse
for quorumId, parcipant := range *list {
for _, participant := range *list {
response = append(response, &responses.SignatureProposalParticipantInvitationEntry{
Title: parcipant.Title,
PubKeyFingerprint: quorumId,
// TODO: Add encryption
EncryptedInvitation: parcipant.InvitationSecret,
Addr: participant.Addr,
})
}
return response

View File

@ -14,13 +14,31 @@ func (m *SigningProposalFSM) actionInitSigningProposal(inEvent fsm.Event, args .
m.payloadMu.Lock()
defer m.payloadMu.Unlock()
m.payload.SigningProposalPayload = &internal.SigningConfirmation{
Quorum: make(internal.SigningProposalQuorum),
if len(args) != 1 {
err = errors.New("{arg0} required {DefaultRequest}")
return
}
for _, participant := range m.payload.SignatureProposalPayload.Quorum {
m.payload.SigningProposalPayload.Quorum[participant.ParticipantId] = &internal.SigningProposalParticipant{
Title: participant.Title,
request, ok := args[0].(requests.DefaultRequest)
if !ok {
err = errors.New("cannot cast {arg0} to type {DefaultRequest}")
return
}
if err = request.Validate(); err != nil {
return
}
m.payload.SigningProposalPayload = &internal.SigningConfirmation{
Quorum: make(internal.SigningProposalQuorum),
CreatedAt: request.CreatedAt,
ExpiresAt: request.CreatedAt.Add(config.SigningConfirmationDeadline),
}
for participantId, participant := range m.payload.SignatureProposalPayload.Quorum {
m.payload.SigningProposalPayload.Quorum[participantId] = &internal.SigningProposalParticipant{
Addr: participant.Addr,
Status: internal.SigningIdle,
UpdatedAt: participant.UpdatedAt,
}
@ -49,6 +67,8 @@ func (m *SigningProposalFSM) actionStartSigningProposal(inEvent fsm.Event, args
return
}
m.payload.SigningProposalPayload.CreatedAt = request.CreatedAt
return
}

View File

@ -23,10 +23,14 @@ const (
StateSigningAwaitPartialKeys = fsm.State("state_signing_await_partial_keys")
// Cancelled
StateSigningPartialKeysAwaitCancelledByTimeout = fsm.State("state_signing_partial_keys_await_cancelled_by_timeout")
StateSigningPartialKeysAwaitCancelledByParticipant = fsm.State("state_signing_partial_keys_await_cancelled_by_participant")
StateSigningPartialKeysAwaitCancelledByTimeout = fsm.State("state_signing_partial_signatures_await_cancelled_by_timeout")
StateSigningPartialKeysAwaitCancelledByParticipant = fsm.State("state_signing_partial_signatures_await_cancelled_by_participant")
StateSigningPartialKeysCollected = fsm.State("state_signing_partial_keys_collected")
StateSigningPartialKeysCollected = fsm.State("state_signing_partial_signatures_collected")
// await full
//
// Events

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

View File

@ -3,6 +3,7 @@ package requests
import (
"errors"
"fmt"
"github.com/depools/dc4bc/fsm/config"
)
@ -20,12 +21,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 {
@ -45,12 +46,8 @@ func (r *SignatureProposalParticipantsListRequest) Validate() error {
}
func (r *SignatureProposalParticipantRequest) Validate() error {
if len(r.PubKeyFingerprint) == 0 {
return errors.New("{PubKeyFingerprint} cannot zero length")
}
if len(r.DecryptedInvitation) == 0 {
return errors.New("{DecryptedInvitation} cannot zero length")
if r.ParticipantId < 0 {
return errors.New("{ParticipantId} cannot be a negative number")
}
if r.CreatedAt.IsZero() {

View File

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

View File

@ -29,6 +29,7 @@ func main() {
var numNodes = 4
for nodeID := 0; nodeID < numNodes; nodeID++ {
var ctx = context.Background()
var userName = fmt.Sprintf("node_%d", nodeID)
var state, err = client.NewLevelDBState(fmt.Sprintf("/tmp/dc4bc_node_%d_state", nodeID))
if err != nil {
log.Fatalf("node %d failed to init state: %v\n", nodeID, err)
@ -39,11 +40,18 @@ func main() {
log.Fatalf("node %d failed to init storage: %v\n", nodeID, err)
}
keyStore, err := client.NewLevelDBKeyStore(userName, fmt.Sprintf("/tmp/dc4bc_node_%d_key_store", nodeID))
if err != nil {
log.Fatalf("Failed to init key store: %v", err)
}
clt, err := client.NewClient(
ctx,
userName,
nil,
state,
stg,
keyStore,
qr.NewCameraProcessor(),
)
if err != nil {

View File

@ -5,10 +5,10 @@
package clientMocks
import (
reflect "reflect"
client "github.com/depools/dc4bc/client"
state_machines "github.com/depools/dc4bc/fsm/state_machines"
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockState is a mock of State interface
@ -64,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 []byte) *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

@ -1,5 +1,6 @@
package mocks
//go:generate mockgen -source=./../client/state.go -destination=./clientMocks/state_mock.go -package=clientMocks
//go:generate mockgen -source=./../client/keystore.go -destination=./clientMocks/keystore_mock.go -package=clientMocks
//go:generate mockgen -source=./../storage/types.go -destination=./storageMocks/storage_mock.go -package=storageMocks
//go:generate mockgen -source=./../qr/qr.go -destination=./qrMocks/qr_mock.go -package=qrMocks

View File

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

View File

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