dc4bc/client/client.go

375 lines
10 KiB
Go
Raw Normal View History

2020-07-29 05:39:02 -07:00
package client
import (
"context"
2020-08-14 05:34:15 -07:00
"crypto/ed25519"
2020-07-29 05:39:02 -07:00
"encoding/json"
2020-08-18 09:41:43 -07:00
"errors"
2020-07-29 05:39:02 -07:00
"fmt"
"log"
"path/filepath"
2020-08-04 00:45:32 -07:00
"sync"
2020-07-29 05:39:02 -07:00
"time"
2020-09-09 06:29:18 -07:00
sipf "github.com/depools/dc4bc/fsm/state_machines/signing_proposal_fsm"
2020-08-21 10:03:42 -07:00
"github.com/depools/dc4bc/client/types"
"github.com/depools/dc4bc/fsm/types/requests"
"github.com/google/uuid"
2020-08-18 09:41:43 -07:00
"github.com/depools/dc4bc/fsm/state_machines/signature_proposal_fsm"
2020-08-19 09:04:41 -07:00
spf "github.com/depools/dc4bc/fsm/state_machines/signature_proposal_fsm"
2020-08-18 09:41:43 -07:00
"github.com/depools/dc4bc/fsm/state_machines"
2020-08-14 05:34:15 -07:00
"github.com/depools/dc4bc/fsm/fsm"
2020-08-19 09:04:41 -07:00
dpf "github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm"
2020-08-03 23:34:10 -07:00
"github.com/depools/dc4bc/qr"
"github.com/depools/dc4bc/storage"
2020-07-29 05:39:02 -07:00
)
2020-07-29 06:32:03 -07:00
const (
pollingPeriod = time.Second
2020-07-30 04:23:09 -07:00
QrCodesDir = "/tmp"
2020-07-29 06:32:03 -07:00
)
2020-07-29 05:39:02 -07:00
2020-09-07 01:07:49 -07:00
type Poller interface {
GetAddr() string
GetPubKey() ed25519.PublicKey
Poll() error
SendMessage(message storage.Message) error
ProcessMessage(message storage.Message) error
GetOperations() (map[string]*types.Operation, error)
GetOperationQRPath(operationID string) ([]string, error)
2020-09-07 01:07:49 -07:00
ReadProcessedOperation() error
StartHTTPServer(listenAddr string) error
GetLogger() *logger
}
2020-07-29 05:39:02 -07:00
type Client struct {
2020-08-04 00:45:32 -07:00
sync.Mutex
2020-09-04 08:35:22 -07:00
Logger *logger
2020-08-14 05:34:15 -07:00
userName string
2020-08-18 09:41:43 -07:00
address string
2020-08-19 09:04:41 -07:00
pubKey ed25519.PublicKey
2020-07-30 04:23:09 -07:00
ctx context.Context
state State
storage storage.Storage
2020-08-14 05:34:15 -07:00
keyStore KeyStore
2020-07-30 04:23:09 -07:00
qrProcessor qr.Processor
2020-07-29 05:39:02 -07:00
}
2020-07-29 06:32:03 -07:00
func NewClient(
ctx context.Context,
2020-08-14 05:34:15 -07:00
userName string,
2020-07-29 06:32:03 -07:00
state State,
storage storage.Storage,
2020-08-14 05:34:15 -07:00
keyStore KeyStore,
2020-07-30 04:23:09 -07:00
qrProcessor qr.Processor,
2020-09-07 01:07:49 -07:00
) (Poller, error) {
2020-08-18 09:41:43 -07:00
keyPair, err := keyStore.LoadKeys(userName, "")
if err != nil {
return nil, fmt.Errorf("failed to LoadKeys: %w", err)
}
2020-07-29 06:32:03 -07:00
return &Client{
2020-07-30 04:23:09 -07:00
ctx: ctx,
2020-09-04 08:35:22 -07:00
Logger: newLogger(userName),
2020-08-14 05:34:15 -07:00
userName: userName,
2020-08-18 09:41:43 -07:00
address: keyPair.GetAddr(),
2020-08-19 09:04:41 -07:00
pubKey: keyPair.Pub,
2020-07-30 04:23:09 -07:00
state: state,
storage: storage,
2020-08-14 05:34:15 -07:00
keyStore: keyStore,
2020-07-30 04:23:09 -07:00
qrProcessor: qrProcessor,
2020-07-29 06:32:03 -07:00
}, nil
2020-07-29 05:39:02 -07:00
}
2020-09-07 01:07:49 -07:00
func (c *Client) GetLogger() *logger {
return c.Logger
}
2020-08-19 09:04:41 -07:00
func (c *Client) GetAddr() string {
return c.address
}
2020-07-29 05:39:02 -07:00
2020-08-19 09:04:41 -07:00
func (c *Client) GetPubKey() ed25519.PublicKey {
return c.pubKey
2020-07-29 05:39:02 -07:00
}
2020-08-09 14:37:53 -07:00
func (c *Client) Poll() error {
2020-07-29 05:39:02 -07:00
tk := time.NewTicker(pollingPeriod)
for {
select {
case <-tk.C:
2020-07-29 06:20:39 -07:00
offset, err := c.state.LoadOffset()
2020-07-29 05:39:02 -07:00
if err != nil {
panic(err)
}
messages, err := c.storage.GetMessages(offset)
if err != nil {
2020-08-09 14:37:53 -07:00
return fmt.Errorf("failed to GetMessages: %w", err)
2020-07-29 05:39:02 -07:00
}
for _, message := range messages {
2020-08-22 05:04:44 -07:00
if message.RecipientAddr == "" || message.RecipientAddr == c.GetAddr() {
2020-09-04 08:35:22 -07:00
c.Logger.Log("Handling message with offset %d, type %s", message.Offset, message.Event)
2020-08-22 05:04:44 -07:00
if err := c.ProcessMessage(message); err != nil {
2020-09-09 06:29:18 -07:00
c.Logger.Log("Failed to process message with offset %d: %v", message.Offset, err)
2020-08-22 05:04:44 -07:00
} else {
2020-09-04 08:35:22 -07:00
c.Logger.Log("Successfully processed message with offset %d, type %s",
2020-08-22 05:04:44 -07:00
message.Offset, message.Event)
}
2020-08-05 08:26:55 -07:00
}
2020-08-14 05:34:15 -07:00
}
case <-c.ctx.Done():
log.Println("Context closed, stop polling...")
return nil
}
}
}
2020-08-05 08:26:55 -07:00
2020-08-19 09:04:41 -07:00
func (c *Client) SendMessage(message storage.Message) error {
if _, err := c.storage.Send(message); err != nil {
return fmt.Errorf("failed to post message: %w", err)
}
return nil
}
2020-08-14 05:34:15 -07:00
func (c *Client) ProcessMessage(message storage.Message) error {
2020-08-18 09:41:43 -07:00
fsmInstance, err := c.getFSMInstance(message.DkgRoundID)
if err != nil {
return fmt.Errorf("failed to getFSMInstance: %w", err)
}
2020-08-21 09:25:09 -07:00
2020-08-18 09:41:43 -07:00
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)
}
}
2020-07-29 06:20:39 -07:00
fsmReq, err := types.FSMRequestFromMessage(message)
2020-08-14 05:34:15 -07:00
if err != nil {
return fmt.Errorf("failed to get FSMRequestFromMessage: %v", err)
}
2020-07-29 05:39:02 -07:00
2020-08-18 09:41:43 -07:00
resp, fsmDump, err := fsmInstance.Do(fsm.Event(message.Event), fsmReq)
2020-08-14 05:34:15 -07:00
if err != nil {
return fmt.Errorf("failed to Do operation in FSM: %w", err)
}
2020-07-29 06:20:39 -07:00
2020-09-04 08:35:22 -07:00
c.Logger.Log("message %s done successfully from %s", message.Event, message.SenderAddr)
2020-08-22 05:04:44 -07:00
2020-08-21 09:25:09 -07:00
if resp.State == spf.StateSignatureProposalCollected {
fsmInstance, err = state_machines.FromDump(fsmDump)
if err != nil {
return fmt.Errorf("failed get state_machines from dump: %w", err)
}
resp, fsmDump, err = fsmInstance.Do(dpf.EventDKGInitProcess, requests.DefaultRequest{
CreatedAt: time.Now(),
})
if err != nil {
return fmt.Errorf("failed to Do operation in FSM: %w", err)
}
}
2020-08-26 08:15:38 -07:00
if resp.State == dpf.StateDkgMasterKeyCollected {
fsmInstance, err = state_machines.FromDump(fsmDump)
if err != nil {
return fmt.Errorf("failed get state_machines from dump: %w", err)
}
2020-08-27 02:45:05 -07:00
resp, fsmDump, err = fsmInstance.Do(sipf.EventSigningInit, requests.DefaultRequest{
2020-08-26 08:15:38 -07:00
CreatedAt: time.Now(),
})
if err != nil {
return fmt.Errorf("failed to Do operation in FSM: %w", err)
}
}
var operation *types.Operation
2020-08-14 05:34:15 -07:00
switch resp.State {
// if the new state is waiting for RPC to airgapped machine
case
2020-08-19 09:04:41 -07:00
spf.StateAwaitParticipantsConfirmations,
dpf.StateDkgCommitsAwaitConfirmations,
dpf.StateDkgDealsAwaitConfirmations,
2020-08-22 05:04:44 -07:00
dpf.StateDkgResponsesAwaitConfirmations,
2020-08-26 08:15:38 -07:00
dpf.StateDkgMasterKeyAwaitConfirmations,
2020-09-01 01:45:19 -07:00
sipf.StateSigningAwaitPartialSigns,
sipf.StateSigningPartialSignsCollected,
2020-08-27 02:45:05 -07:00
sipf.StateSigningAwaitConfirmations:
2020-08-24 07:41:15 -07:00
if resp.Data != nil {
bz, err := json.Marshal(resp.Data)
if err != nil {
return fmt.Errorf("failed to marshal FSM response: %w", err)
}
2020-07-29 05:39:02 -07:00
2020-08-24 07:41:15 -07:00
operation = &types.Operation{
ID: uuid.New().String(),
Type: types.OperationType(resp.State),
Payload: bz,
DKGIdentifier: message.DkgRoundID,
CreatedAt: time.Now(),
}
2020-08-14 05:34:15 -07:00
}
default:
2020-09-04 08:35:22 -07:00
c.Logger.Log("State %s does not require an operation", resp.State)
2020-07-29 05:39:02 -07:00
}
2020-07-29 06:20:39 -07:00
2020-08-14 05:34:15 -07:00
if operation != nil {
2020-08-24 07:41:15 -07:00
if err := c.state.PutOperation(operation); err != nil {
return fmt.Errorf("failed to PutOperation: %w", err)
2020-07-29 05:39:02 -07:00
}
}
2020-08-14 05:34:15 -07:00
2020-08-21 09:25:09 -07:00
if err := c.state.SaveOffset(message.Offset + 1); err != nil {
2020-08-14 05:34:15 -07:00
return fmt.Errorf("failed to SaveOffset: %w", err)
}
2020-08-18 09:41:43 -07:00
if err := c.state.SaveFSM(message.DkgRoundID, fsmDump); err != nil {
2020-08-14 05:34:15 -07:00
return fmt.Errorf("failed to SaveFSM: %w", err)
}
return nil
2020-07-29 05:39:02 -07:00
}
func (c *Client) GetOperations() (map[string]*types.Operation, error) {
2020-07-29 05:39:02 -07:00
return c.state.GetOperations()
}
2020-07-31 07:55:47 -07:00
func (c *Client) getOperationJSON(operationID string) ([]byte, error) {
2020-07-29 05:39:02 -07:00
operation, err := c.state.GetOperationByID(operationID)
if err != nil {
2020-07-31 07:55:47 -07:00
return nil, fmt.Errorf("failed to get operation: %w", err)
2020-07-29 05:39:02 -07:00
}
operationJSON, err := json.Marshal(operation)
if err != nil {
2020-07-31 07:55:47 -07:00
return nil, fmt.Errorf("failed to marshal operation: %w", err)
}
return operationJSON, nil
}
// GetOperationQRPath returns a path to the image with the QR generated
// for the specified operation. It is supposed that the user will open
// this file herself.
2020-09-01 08:06:37 -07:00
func (c *Client) GetOperationQRPath(operationID string) ([]string, error) {
2020-07-31 07:55:47 -07:00
operationJSON, err := c.getOperationJSON(operationID)
if err != nil {
2020-09-01 08:06:37 -07:00
return nil, fmt.Errorf("failed to get operation in JSON: %w", err)
2020-07-29 05:39:02 -07:00
}
2020-07-30 04:23:09 -07:00
operationQRPath := filepath.Join(QrCodesDir, operationID)
2020-09-01 08:06:37 -07:00
chunks, err := qr.DataToChunks(operationJSON)
if err != nil {
return nil, fmt.Errorf("failed to divide a data on chunks: %w", err)
}
qrs := make([]string, 0, len(chunks))
for idx, chunk := range chunks {
qrPath := fmt.Sprintf("%s-%d", operationQRPath, idx)
if err = c.qrProcessor.WriteQR(qrPath, chunk); err != nil {
return nil, err
}
qrs = append(qrs, qrPath)
2020-07-29 05:39:02 -07:00
}
2020-09-01 08:06:37 -07:00
return qrs, nil
2020-07-29 05:39:02 -07:00
}
2020-07-29 06:20:39 -07:00
// ReadProcessedOperation reads the processed operation from camera, checks that
// the processed operation has its unprocessed counterpart in our state,
2020-07-30 04:26:40 -07:00
// posts a Message to the storage and deletes the operation from our state.
2020-07-29 06:20:39 -07:00
func (c *Client) ReadProcessedOperation() error {
2020-09-01 08:06:37 -07:00
bz, err := qr.ReadDataFromQRChunks(c.qrProcessor)
2020-07-29 06:20:39 -07:00
if err != nil {
2020-07-30 04:27:47 -07:00
return fmt.Errorf("failed to ReadQR: %s", err)
2020-07-29 06:20:39 -07:00
}
var operation types.Operation
2020-07-29 06:20:39 -07:00
if err = json.Unmarshal(bz, &operation); err != nil {
return fmt.Errorf("failed to unmarshal processed operation")
}
2020-07-31 07:55:47 -07:00
return c.handleProcessedOperation(operation)
}
func (c *Client) handleProcessedOperation(operation types.Operation) error {
2020-08-25 05:56:27 -07:00
storedOperation, err := c.state.GetOperationByID(operation.ID)
if err != nil {
return fmt.Errorf("failed to find matching operation: %w", err)
2020-08-14 05:34:15 -07:00
}
2020-08-25 05:56:27 -07:00
if err := storedOperation.Check(&operation); err != nil {
return fmt.Errorf("processed operation does not match stored operation: %w", err)
2020-08-07 06:54:44 -07:00
}
2020-08-05 08:26:55 -07:00
2020-08-25 05:56:27 -07:00
for _, message := range operation.ResultMsgs {
message.SenderAddr = c.GetAddr()
sig, err := c.signMessage(message.Bytes())
if err != nil {
return fmt.Errorf("failed to sign a message: %w", err)
}
message.Signature = sig
if _, err := c.storage.Send(message); err != nil {
return fmt.Errorf("failed to post message: %w", err)
}
2020-07-29 06:20:39 -07:00
}
if err := c.state.DeleteOperation(operation.ID); err != nil {
return fmt.Errorf("failed to DeleteOperation: %w", err)
}
return nil
}
2020-08-07 06:54:44 -07:00
2020-08-18 09:41:43 -07:00
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
2020-08-07 06:54:44 -07:00
}
2020-08-14 05:34:15 -07:00
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)
}
2020-08-07 06:54:44 -07:00
2020-08-14 05:34:15 -07:00
return ed25519.Sign(keyPair.Priv, message), nil
2020-08-07 06:54:44 -07:00
}
2020-08-18 09:41:43 -07:00
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")
}
2020-08-07 06:54:44 -07:00
return nil
}