dc4bc/client/client.go

432 lines
13 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-11-05 01:43:54 -08:00
"github.com/lidofinance/dc4bc/fsm/types/responses"
2020-10-05 08:08:55 -07:00
2020-11-05 01:43:54 -08:00
sipf "github.com/lidofinance/dc4bc/fsm/state_machines/signing_proposal_fsm"
2020-09-09 06:29:18 -07:00
2020-08-21 10:03:42 -07:00
"github.com/google/uuid"
2020-11-05 01:43:54 -08:00
"github.com/lidofinance/dc4bc/client/types"
"github.com/lidofinance/dc4bc/fsm/types/requests"
2020-08-21 10:03:42 -07:00
2020-11-05 01:43:54 -08:00
spf "github.com/lidofinance/dc4bc/fsm/state_machines/signature_proposal_fsm"
2020-08-18 09:41:43 -07:00
2020-11-05 01:43:54 -08:00
"github.com/lidofinance/dc4bc/fsm/state_machines"
2020-08-18 09:41:43 -07:00
2020-11-05 01:43:54 -08:00
"github.com/lidofinance/dc4bc/fsm/fsm"
dpf "github.com/lidofinance/dc4bc/fsm/state_machines/dkg_proposal_fsm"
"github.com/lidofinance/dc4bc/qr"
"github.com/lidofinance/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-10-05 08:00:54 -07:00
type Client interface {
2020-09-07 01:07:49 -07:00
Poll() error
2020-10-05 08:00:54 -07:00
GetLogger() *logger
GetPubKey() ed25519.PublicKey
GetUsername() string
2020-09-07 01:07:49 -07:00
SendMessage(message storage.Message) error
ProcessMessage(message storage.Message) error
GetOperations() (map[string]*types.Operation, error)
2020-09-29 08:16:20 -07:00
GetOperationQRPath(operationID string) (string, error)
2020-09-07 01:07:49 -07:00
StartHTTPServer(listenAddr string) error
}
2020-10-05 08:00:54 -07:00
type BaseClient 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-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-10-05 08:00:54 -07:00
) (Client, 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-10-05 08:00:54 -07:00
return &BaseClient{
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-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-10-05 08:00:54 -07:00
func (c *BaseClient) GetLogger() *logger {
2020-09-07 01:07:49 -07:00
return c.Logger
}
2020-10-05 08:00:54 -07:00
func (c *BaseClient) GetUsername() string {
return c.userName
2020-08-19 09:04:41 -07:00
}
2020-07-29 05:39:02 -07:00
2020-10-05 08:00:54 -07:00
func (c *BaseClient) GetPubKey() ed25519.PublicKey {
2020-08-19 09:04:41 -07:00
return c.pubKey
2020-07-29 05:39:02 -07:00
}
2020-09-28 01:01:40 -07:00
// Poll is a main client loop, which gets new messages from an append-only log and processes them
2020-10-05 08:00:54 -07:00
func (c *BaseClient) 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 {
if message.RecipientAddr == "" || message.RecipientAddr == c.GetUsername() {
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-10-05 08:00:54 -07:00
func (c *BaseClient) SendMessage(message storage.Message) error {
2020-08-19 09:04:41 -07:00
if _, err := c.storage.Send(message); err != nil {
return fmt.Errorf("failed to post message: %w", err)
}
return nil
}
// processSignature saves a broadcasted reconstructed signature to a LevelDB
2020-10-05 08:08:55 -07:00
func (c *BaseClient) processSignature(message storage.Message) error {
var (
signature types.ReconstructedSignature
err error
)
if err = json.Unmarshal(message.Data, &signature); err != nil {
return fmt.Errorf("failed to unmarshal reconstructed signature: %w", err)
}
2020-11-02 02:56:02 -08:00
signature.Username = message.SenderAddr
signature.DKGRoundID = message.DkgRoundID
return c.state.SaveSignature(signature)
}
2020-10-05 08:00:54 -07:00
func (c *BaseClient) ProcessMessage(message storage.Message) error {
2020-11-02 05:17:01 -08:00
// save broadcasted reconstructed signature
if fsm.Event(message.Event) == types.SignatureReconstructed {
if err := c.processSignature(message); err != nil {
return fmt.Errorf("failed to process signature: %w", err)
}
if err := c.state.SaveOffset(message.Offset + 1); err != nil {
return fmt.Errorf("failed to SaveOffset: %w", err)
}
return nil
}
2020-11-02 05:17:01 -08:00
// save signing data to the same storage as we save signatures
// This allows easy to view signing data by CLI-command
2020-11-02 02:56:02 -08:00
if fsm.Event(message.Event) == sipf.EventSigningStart {
if err := c.processSignature(message); err != nil {
return fmt.Errorf("failed to process signature: %w", err)
}
}
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-09-22 06:15:18 -07:00
// we can't verify a message at this moment, cause we don't have public keys of participantss
2020-09-29 07:31:31 -07:00
if fsm.Event(message.Event) != spf.EventInitProposal {
2020-08-18 09:41:43 -07:00
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-09-22 06:15:18 -07:00
// switch FSM state by hand due to implementation specifics
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 {
// if we are initiator of signing, then we don't need to confirm our participation
if data, ok := resp.Data.(responses.SigningProposalParticipantInvitationsResponse); ok {
initiator, err := fsmInstance.SigningQuorumGetParticipant(data.InitiatorId)
if err != nil {
return fmt.Errorf("failed to get SigningQuorumParticipant: %w", err)
}
if initiator.Username == c.GetUsername() {
break
}
}
2020-08-24 07:41:15 -07:00
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
// switch FSM state by hand due to implementation specifics
if resp.State == sipf.StateSigningPartialSignsCollected {
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(sipf.EventSigningRestart, requests.DefaultRequest{
CreatedAt: time.Now(),
})
if err != nil {
return fmt.Errorf("failed to Do operation in FSM: %w", err)
}
}
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
}
2020-10-05 08:00:54 -07:00
func (c *BaseClient) GetOperations() (map[string]*types.Operation, error) {
2020-07-29 05:39:02 -07:00
return c.state.GetOperations()
}
//GetSignatures returns all signatures for the given DKG round that were reconstructed on the airgapped machine and
// broadcasted by users
2020-10-05 08:08:55 -07:00
func (c *BaseClient) GetSignatures(dkgID string) (map[string][]types.ReconstructedSignature, error) {
return c.state.GetSignatures(dkgID)
}
//GetSignatureByDataHash returns a list of reconstructed signatures of the signed data broadcasted by users
2020-11-02 01:56:40 -08:00
func (c *BaseClient) GetSignatureByID(dkgID, sigID string) ([]types.ReconstructedSignature, error) {
return c.state.GetSignatureByID(dkgID, sigID)
}
2020-09-28 01:01:40 -07:00
// getOperationJSON returns a specific JSON-encoded operation
2020-10-05 08:00:54 -07:00
func (c *BaseClient) 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-10-05 08:00:54 -07:00
func (c *BaseClient) GetOperationQRPath(operationID string) (string, error) {
2020-07-31 07:55:47 -07:00
operationJSON, err := c.getOperationJSON(operationID)
if err != nil {
2020-09-29 08:16:20 -07:00
return "", fmt.Errorf("failed to get operation in JSON: %w", err)
2020-07-29 05:39:02 -07:00
}
2020-09-11 04:39:01 -07:00
operationQRPath := filepath.Join(QrCodesDir, fmt.Sprintf("dc4bc_qr_%s", operationID))
2020-09-01 08:06:37 -07:00
2020-09-29 08:16:20 -07:00
qrPath := fmt.Sprintf("%s.gif", operationQRPath)
if err = c.qrProcessor.WriteQR(qrPath, operationJSON); err != nil {
return "", err
2020-07-29 05:39:02 -07:00
}
2020-09-29 08:16:20 -07:00
return qrPath, nil
2020-07-29 05:39:02 -07:00
}
2020-07-29 06:20:39 -07:00
2020-09-22 06:15:18 -07:00
// handleProcessedOperation handles an operation which was processed by the airgapped machine
// It checks that the operation exists in an operation pool, signs the operation, sends it to an append-only log and
// deletes it from the pool.
2020-10-05 08:00:54 -07:00
func (c *BaseClient) 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.GetUsername()
2020-08-25 05:56:27 -07:00
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-09-22 06:15:18 -07:00
// getFSMInstance returns a FSM for a necessary DKG round.
2020-10-05 08:00:54 -07:00
func (c *BaseClient) getFSMInstance(dkgRoundID string) (*state_machines.FSMInstance, error) {
2020-08-18 09:41:43 -07:00
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-10-05 08:00:54 -07:00
func (c *BaseClient) signMessage(message []byte) ([]byte, error) {
2020-08-14 05:34:15 -07:00
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-10-05 08:00:54 -07:00
func (c *BaseClient) verifyMessage(fsmInstance *state_machines.FSMInstance, message storage.Message) error {
senderPubKey, err := fsmInstance.GetPubKeyByUsername(message.SenderAddr)
2020-08-18 09:41:43 -07:00
if err != nil {
return fmt.Errorf("failed to GetPubKeyByUsername: %w", err)
2020-08-18 09:41:43 -07:00
}
if !ed25519.Verify(senderPubKey, message.Bytes(), message.Signature) {
return errors.New("signature is corrupt")
}
2020-08-07 06:54:44 -07:00
return nil
}
func (c *BaseClient) GetFSMDump(dkgID string) (*state_machines.FSMDump, error) {
fsmInstance, err := c.getFSMInstance(dkgID)
if err != nil {
return nil, fmt.Errorf("failed to get FSM instance for DKG round ID %s: %w", dkgID, err)
}
return fsmInstance.FSMDump(), nil
}