mirror of https://github.com/certusone/dc4bc.git
Merge branch 'fsm-draft' into feat/airgapped-qr
This commit is contained in:
commit
0a42c75725
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
204
client/client.go
204
client/client.go
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package requests
|
||||
|
||||
import "time"
|
||||
|
||||
type DefaultRequest struct {
|
||||
CreatedAt time.Time
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
8
main.go
8
main.go
|
@ -29,6 +29,7 @@ func main() {
|
|||
var numNodes = 4
|
||||
for nodeID := 0; nodeID < numNodes; nodeID++ {
|
||||
var ctx = context.Background()
|
||||
var userName = fmt.Sprintf("node_%d", nodeID)
|
||||
var state, err = client.NewLevelDBState(fmt.Sprintf("/tmp/dc4bc_node_%d_state", nodeID))
|
||||
if err != nil {
|
||||
log.Fatalf("node %d failed to init state: %v\n", nodeID, err)
|
||||
|
@ -39,11 +40,18 @@ func main() {
|
|||
log.Fatalf("node %d failed to init storage: %v\n", nodeID, err)
|
||||
}
|
||||
|
||||
keyStore, err := client.NewLevelDBKeyStore(userName, fmt.Sprintf("/tmp/dc4bc_node_%d_key_store", nodeID))
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to init key store: %v", err)
|
||||
}
|
||||
|
||||
clt, err := client.NewClient(
|
||||
ctx,
|
||||
userName,
|
||||
nil,
|
||||
state,
|
||||
stg,
|
||||
keyStore,
|
||||
qr.NewCameraProcessor(),
|
||||
)
|
||||
if err != nil {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue