diff --git a/airgapped/airgapped.go b/airgapped/airgapped.go index 77d237c..c1e69d3 100644 --- a/airgapped/airgapped.go +++ b/airgapped/airgapped.go @@ -13,9 +13,9 @@ import ( "github.com/depools/dc4bc/qr" "go.dedis.ch/kyber/v3" "go.dedis.ch/kyber/v3/encrypt/ecies" - "go.dedis.ch/kyber/v3/group/edwards25519" "go.dedis.ch/kyber/v3/pairing/bn256" dkgPedersen "go.dedis.ch/kyber/v3/share/dkg/pedersen" + "log" "sync" ) @@ -57,7 +57,6 @@ func (am *AirgappedMachine) getParticipantID(dkgIdentifier string) (int, error) } func (am *AirgappedMachine) encryptData(dkgIdentifier, to string, data []byte) ([]byte, error) { - suite := edwards25519.NewBlakeSHA256Ed25519() dkgInstance, ok := am.dkgInstances[dkgIdentifier] if !ok { return nil, fmt.Errorf("invalid dkg identifier: %s", dkgIdentifier) @@ -68,7 +67,7 @@ func (am *AirgappedMachine) encryptData(dkgIdentifier, to string, data []byte) ( return nil, fmt.Errorf("failed to get pk for participant %s: %w", to, err) } - encryptedData, err := ecies.Encrypt(suite, pk, data, suite.Hash) + encryptedData, err := ecies.Encrypt(am.suite, pk, data, am.suite.Hash) if err != nil { return nil, fmt.Errorf("failed to encrypt data: %w", err) } @@ -76,9 +75,7 @@ func (am *AirgappedMachine) encryptData(dkgIdentifier, to string, data []byte) ( } func (am *AirgappedMachine) decryptData(data []byte) ([]byte, error) { - suite := edwards25519.NewBlakeSHA256Ed25519() - - decryptedData, err := ecies.Decrypt(suite, am.secKey, data, suite.Hash) + decryptedData, err := ecies.Decrypt(am.suite, am.secKey, data, am.suite.Hash) if err != nil { return nil, fmt.Errorf("failed to decrypt data: %w", err) } @@ -115,6 +112,10 @@ func (am *AirgappedMachine) GetPubKey() kyber.Point { return am.pubKey } +type Commits struct { + MarshaledCommit []byte +} + func (am *AirgappedMachine) handleStateDkgCommitsAwaitConfirmations(o *client.Operation) error { var ( payload responses.SignatureProposalParticipantStatusResponse @@ -142,28 +143,36 @@ func (am *AirgappedMachine) handleStateDkgCommitsAwaitConfirmations(o *client.Op return fmt.Errorf("failed to init dkg instance: %w", err) } - commits, err := json.Marshal(dkgInstance.GetCommits()) - if err != nil { - return fmt.Errorf("failed to marshal commits: %w", err) + // TODO: come up with something better + var commits []Commits + dkgCommits := dkgInstance.GetCommits() + for _, commit := range dkgCommits { + commitBz, err := commit.MarshalBinary() + if err != nil { + return fmt.Errorf("failed to marshal commits: %w", err) + } + commits = append(commits, Commits{MarshaledCommit: commitBz}) } + commitsBz, err := json.Marshal(commits) am.dkgInstances[o.DKGIdentifier] = dkgInstance req := requests.DKGProposalCommitConfirmationRequest{ ParticipantId: dkgInstance.ParticipantID, - Commit: commits, + Commit: commitsBz, CreatedAt: o.CreatedAt, } - reqBz, err := client.FSMRequestToBytes(dkg_proposal_fsm.EventDKGCommitConfirmationReceived, req) + reqBz, err := json.Marshal(req) if err != nil { return fmt.Errorf("failed to generate fsm request: %w", err) } o.Result = reqBz + o.Event = dkg_proposal_fsm.EventDKGCommitConfirmationReceived return nil } // We have many deals which should be sent privately to a required participant, so func returns a slice of operations -func (am *AirgappedMachine) handleStateDkgDealsAwaitConfirmations(o *client.Operation) ([]client.Operation, error) { +func (am *AirgappedMachine) handleStateDkgDealsAwaitConfirmations(o client.Operation) ([]client.Operation, error) { var ( payload responses.DKGProposalCommitParticipantResponse err error @@ -179,11 +188,19 @@ func (am *AirgappedMachine) handleStateDkgDealsAwaitConfirmations(o *client.Oper } for _, entry := range payload { - var commits []kyber.Point + var commits []Commits if err = json.Unmarshal(entry.Commit, &commits); err != nil { return nil, fmt.Errorf("failed to unmarshal commits: %w", err) } - dkgInstance.StoreCommits(entry.Title, commits) + dkgCommits := make([]kyber.Point, 0, len(commits)) + for _, c := range commits { + commit := am.suite.Point() + if err = commit.UnmarshalBinary(c.MarshaledCommit); err != nil { + return nil, fmt.Errorf("failed to unmarshal commit: %w", err) + } + dkgCommits = append(dkgCommits, commit) + } + dkgInstance.StoreCommits(entry.Title, dkgCommits) } deals, err := dkgInstance.GetDeals() @@ -212,12 +229,13 @@ func (am *AirgappedMachine) handleStateDkgDealsAwaitConfirmations(o *client.Oper CreatedAt: o.CreatedAt, } o.To = toParticipant - reqBz, err := client.FSMRequestToBytes(dkg_proposal_fsm.EventDKGDealConfirmationReceived, req) + reqBz, err := json.Marshal(req) if err != nil { return nil, fmt.Errorf("failed to generate fsm request: %w", err) } o.Result = reqBz - operations = append(operations, *o) + o.Event = dkg_proposal_fsm.EventDKGDealConfirmationReceived + operations = append(operations, o) } return operations, nil } @@ -267,12 +285,13 @@ func (am *AirgappedMachine) handleStateDkgResponsesAwaitConfirmations(o *client. CreatedAt: o.CreatedAt, } - reqBz, err := client.FSMRequestToBytes(dkg_proposal_fsm.EventDKGResponseConfirmationReceived, req) + reqBz, err := json.Marshal(req) if err != nil { return fmt.Errorf("failed to generate fsm request: %w", err) } o.Result = reqBz + o.Event = dkg_proposal_fsm.EventDKGResponseConfirmationReceived return nil } @@ -320,11 +339,12 @@ func (am *AirgappedMachine) handleStateDkgMasterKeyAwaitConfirmations(o *client. MasterKey: masterPubKeyBz, CreatedAt: o.CreatedAt, } - reqBz, err := client.FSMRequestToBytes(dkg_proposal_fsm.EventDKGMasterKeyConfirmationReceived, req) + reqBz, err := json.Marshal(req) if err != nil { return fmt.Errorf("failed to generate fsm request: %w", err) } o.Result = reqBz + o.Event = dkg_proposal_fsm.EventDKGMasterKeyConfirmationReceived return nil } @@ -348,7 +368,7 @@ func (am *AirgappedMachine) HandleOperation(operation client.Operation) ([]clien case dkg_proposal_fsm.StateDkgCommitsAwaitConfirmations: err = am.handleStateDkgCommitsAwaitConfirmations(&operation) case dkg_proposal_fsm.StateDkgDealsAwaitConfirmations: - operations, err = am.handleStateDkgDealsAwaitConfirmations(&operation) + operations, err = am.handleStateDkgDealsAwaitConfirmations(operation) case dkg_proposal_fsm.StateDkgResponsesAwaitConfirmations: err = am.handleStateDkgResponsesAwaitConfirmations(&operation) case dkg_proposal_fsm.StateDkgMasterKeyAwaitConfirmations: @@ -359,6 +379,8 @@ func (am *AirgappedMachine) HandleOperation(operation client.Operation) ([]clien // if we have error after handling the operation, we write the error to the operation, so we can feed it to a FSM if err != nil { + log.Println(fmt.Sprintf("failed to handle operation %s, returning response with errot to client: %v", + operation.Type, err)) if e := am.writeErrorRequestToOperation(&operation, err); e != nil { return nil, fmt.Errorf("failed to write error request to an operation: %w", e) } @@ -431,10 +453,11 @@ func (am *AirgappedMachine) writeErrorRequestToOperation(o *client.Operation, ha CreatedAt: o.CreatedAt, } errorEvent := eventToErrorMap[fsm.State(o.Type)] - reqBz, err := client.FSMRequestToBytes(errorEvent, req) + reqBz, err := json.Marshal(req) if err != nil { return fmt.Errorf("failed to generate fsm request: %w", err) } o.Result = reqBz + o.Event = errorEvent return nil } diff --git a/airgapped/airgapped_test.go b/airgapped/airgapped_test.go new file mode 100644 index 0000000..8aa6853 --- /dev/null +++ b/airgapped/airgapped_test.go @@ -0,0 +1,235 @@ +package airgapped + +import ( + "encoding/json" + "fmt" + "github.com/depools/dc4bc/client" + "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/fsm/types/responses" + "github.com/google/uuid" + "sync" + "testing" + "time" +) + +type Node struct { + ParticipantID int + Participant string + Machine *AirgappedMachine + commits []requests.DKGProposalCommitConfirmationRequest + deals []requests.DKGProposalDealConfirmationRequest + responses []requests.DKGProposalResponseConfirmationRequest + masterKey []requests.DKGProposalMasterKeyConfirmationRequest +} + +func (n *Node) storeOperation(t *testing.T, o client.Operation) { + switch o.Event { + case dkg_proposal_fsm.EventDKGCommitConfirmationReceived: + var req requests.DKGProposalCommitConfirmationRequest + if err := json.Unmarshal(o.Result, &req); err != nil { + t.Errorf("failed to unmarshal fsm req: %v", err) + } + n.commits = append(n.commits, req) + case dkg_proposal_fsm.EventDKGDealConfirmationReceived: + var req requests.DKGProposalDealConfirmationRequest + if err := json.Unmarshal(o.Result, &req); err != nil { + t.Errorf("failed to unmarshal fsm req: %v", err) + } + n.deals = append(n.deals, req) + case dkg_proposal_fsm.EventDKGResponseConfirmationReceived: + var req requests.DKGProposalResponseConfirmationRequest + if err := json.Unmarshal(o.Result, &req); err != nil { + t.Errorf("failed to unmarshal fsm req: %v", err) + } + n.responses = append(n.responses, req) + case dkg_proposal_fsm.EventDKGMasterKeyConfirmationReceived: + var req requests.DKGProposalMasterKeyConfirmationRequest + if err := json.Unmarshal(o.Result, &req); err != nil { + t.Errorf("failed to unmarshal fsm req: %v", err) + } + n.masterKey = append(n.masterKey, req) + default: + t.Errorf("invalid event: %s", o.Event) + } +} + +type Transport struct { + nodes []*Node +} + +func (tr *Transport) BroadcastOperation(t *testing.T, operation client.Operation) { + for _, node := range tr.nodes { + if operation.To == "" || operation.To == node.Participant { + node.storeOperation(t, operation) + } + } +} + +func createOperation(t *testing.T, opType string, to string, req interface{}) client.Operation { + reqBz, err := json.Marshal(req) + if err != nil { + t.Errorf("failed to marshal request: %v", err) + } + op := client.Operation{ + ID: uuid.New().String(), + Type: client.OperationType(opType), + Payload: reqBz, + Result: nil, + CreatedAt: time.Now(), + DKGIdentifier: "dkg_identifier", + To: to, + } + return op +} + +func TestAirgappedFullDKG(t *testing.T) { + nodesCount := 4 + participants := make([]string, nodesCount) + for i := 0; i < nodesCount; i++ { + participants[i] = fmt.Sprintf("Participant#%d", i) + } + + tr := &Transport{} + for i := 0; i < nodesCount; i++ { + am := NewAirgappedMachine() + node := Node{ + ParticipantID: i, + Participant: participants[i], + Machine: am, + } + tr.nodes = append(tr.nodes, &node) + } + + var initReq responses.SignatureProposalParticipantInvitationsResponse + for _, n := range tr.nodes { + entry := &responses.SignatureProposalParticipantInvitationEntry{ + ParticipantId: n.ParticipantID, + Title: n.Participant, + } + initReq = append(initReq, entry) + } + op := createOperation(t, string(signature_proposal_fsm.StateAwaitParticipantsConfirmations), "", initReq) + runStep(tr, func(n *Node, wg *sync.WaitGroup) { + defer wg.Done() + + _, err := n.Machine.HandleOperation(op) + if err != nil { + t.Errorf("%s: failed to handle operation %s: %v", n.Participant, op.Type, err) + } + }) + + // get commits + var getCommitsRequest responses.SignatureProposalParticipantStatusResponse + for _, n := range tr.nodes { + pubKey, err := n.Machine.pubKey.MarshalBinary() + if err != nil { + t.Errorf("%s: failed to marshal pubkey: %v", n.Participant, err) + } + entry := &responses.SignatureProposalParticipantStatusEntry{ + ParticipantId: n.ParticipantID, + Title: n.Participant, + DkgPubKey: pubKey, + } + getCommitsRequest = append(getCommitsRequest, entry) + } + op = createOperation(t, string(dkg_proposal_fsm.StateDkgCommitsAwaitConfirmations), "", getCommitsRequest) + runStep(tr, func(n *Node, wg *sync.WaitGroup) { + defer wg.Done() + + operations, err := n.Machine.HandleOperation(op) + if err != nil { + t.Errorf("%s: failed to handle operation %s: %v", n.Participant, op.Type, err) + } + for _, op := range operations { + tr.BroadcastOperation(t, op) + } + }) + + //deals + runStep(tr, func(n *Node, wg *sync.WaitGroup) { + defer wg.Done() + + var payload responses.DKGProposalCommitParticipantResponse + for _, req := range n.commits { + p := responses.DKGProposalCommitParticipantEntry{ + ParticipantId: req.ParticipantId, + Title: fmt.Sprintf("Participant#%d", req.ParticipantId), + Commit: req.Commit, + } + payload = append(payload, &p) + } + op := createOperation(t, string(dkg_proposal_fsm.StateDkgDealsAwaitConfirmations), "", payload) + + operations, err := n.Machine.HandleOperation(op) + if err != nil { + t.Errorf("%s: failed to handle operation %s: %v", n.Participant, op.Type, err) + } + for _, op := range operations { + tr.BroadcastOperation(t, op) + } + }) + + //responses + runStep(tr, func(n *Node, wg *sync.WaitGroup) { + defer wg.Done() + + var payload responses.DKGProposalDealParticipantResponse + for _, req := range n.deals { + p := responses.DKGProposalDealParticipantEntry{ + ParticipantId: req.ParticipantId, + Title: fmt.Sprintf("Participant#%d", req.ParticipantId), + Deal: req.Deal, + } + payload = append(payload, &p) + } + op := createOperation(t, string(dkg_proposal_fsm.StateDkgResponsesAwaitConfirmations), "", payload) + + operations, err := n.Machine.HandleOperation(op) + if err != nil { + t.Errorf("%s: failed to handle operation %s: %v", n.Participant, op.Type, err) + } + for _, op := range operations { + tr.BroadcastOperation(t, op) + } + }) + + //master key + runStep(tr, func(n *Node, wg *sync.WaitGroup) { + defer wg.Done() + + var payload responses.DKGProposalResponsesParticipantResponse + for _, req := range n.responses { + p := responses.DKGProposalResponsesParticipantEntry{ + ParticipantId: req.ParticipantId, + Title: fmt.Sprintf("Participant#%d", req.ParticipantId), + Responses: req.Response, + } + payload = append(payload, &p) + } + op := createOperation(t, string(dkg_proposal_fsm.StateDkgMasterKeyAwaitConfirmations), "", payload) + + operations, err := n.Machine.HandleOperation(op) + if err != nil { + t.Errorf("%s: failed to handle operation %s: %v", n.Participant, op.Type, err) + } + for _, op := range operations { + tr.BroadcastOperation(t, op) + } + }) + + for _, n := range tr.nodes { + fmt.Println(n.masterKey) + } +} + +func runStep(transport *Transport, cb func(n *Node, wg *sync.WaitGroup)) { + var wg = &sync.WaitGroup{} + for _, node := range transport.nodes { + wg.Add(1) + n := node + go cb(n, wg) + } + wg.Wait() +} diff --git a/client/types.go b/client/types.go index 6a2f86e..c5af245 100644 --- a/client/types.go +++ b/client/types.go @@ -22,6 +22,7 @@ type Operation struct { CreatedAt time.Time DKGIdentifier string To string + Event fsm.Event } func (o *Operation) Check(o2 *Operation) error {