mirror of https://github.com/certusone/dc4bc.git
Merge branch 'master' into feat/follow-ups
# Conflicts: # client/client.go # client/http_server.go # client/types/types.go
This commit is contained in:
commit
32fde1b880
47
HowTo.md
47
HowTo.md
|
@ -148,14 +148,12 @@ The command returns a hash of the proposing message. If it is not equal to the h
|
|||
Copy the Operation ID and make the node produce a QR-code for it:
|
||||
```
|
||||
$ ./dc4bc_cli get_operation_qr 6d98f39d-1b24-49ce-8473-4f5d934ab2dc --listen_addr localhost:8080
|
||||
List of paths to QR codes for operation 6d98f39d-1b24-49ce-8473-4f5d934ab2dc:
|
||||
0) QR code: /tmp/dc4bc_qr_6d98f39d-1b24-49ce-8473-4f5d934ab2dc-0
|
||||
1) QR code: /tmp/dc4bc_qr_6d98f39d-1b24-49ce-8473-4f5d934ab2dc-1
|
||||
QR code was saved to: /tmp/dc4bc_qr_6d98f39d-1b24-49ce-8473-4f5d934ab2dc-0.gif
|
||||
```
|
||||
|
||||
A single operation might be split into several QR-codes. Open the first QR code in your default image viewer and take a photo of it:
|
||||
A single operation might be split into several QR-codes, which will be located in a single GIF file. Open the GIF-animation in any gif viewer and take a video of it:
|
||||
```
|
||||
open /tmp/dc4bc_qr_6d98f39d-1b24-49ce-8473-4f5d934ab2dc-0
|
||||
open /tmp/dc4bc_qr_6d98f39d-1b24-49ce-8473-4f5d934ab2dc-0.gif
|
||||
```
|
||||
|
||||
Now go to `dc4bc_airgapped` prompt and enter:
|
||||
|
@ -164,25 +162,19 @@ Now go to `dc4bc_airgapped` prompt and enter:
|
|||
>>> read_qr
|
||||
```
|
||||
|
||||
A new window will be opened showing what your laptop's camera sees. Place the photo of the QR from the previous step in front of the camera and wait for the airgapped machine to scan it. You will have to scan all operation QR codes that were produced by the node.
|
||||
A new window will be opened showing what your laptop's camera sees. Place the animation of the QR-gif from the previous step in front of the camera and wait for the airgapped machine to scan it (progress can be seen in window's title).
|
||||
|
||||
After you've scanned all QR codes, you will be shown the path to the QR code that contains the response of `dc4bc_airgapped`. Note that it may be split into several chunks:
|
||||
After you've scanned all QR codes, you will be shown the path to the QR code that contains the response of `dc4bc_airgapped`. Note that it may be split into several chunks like on the client:
|
||||
```
|
||||
An operation in the readed QR code handled successfully, a result operation saved by chunks in following qr codes:
|
||||
Operation's chunk #0: result_qr_codes/state_sig_proposal_await_participants_confirmations_de09e754-3bc8-4e67-9651-dcdba3316dba_-0.png
|
||||
Operation's chunk #1: result_qr_codes/state_sig_proposal_await_participants_confirmations_de09e754-3bc8-4e67-9651-dcdba3316dba_-1.png
|
||||
Operation's chunk #2: result_qr_codes/state_sig_proposal_await_participants_confirmations_de09e754-3bc8-4e67-9651-dcdba3316dba_-2.png
|
||||
Operation's chunk #3: result_qr_codes/state_sig_proposal_await_participants_confirmations_de09e754-3bc8-4e67-9651-dcdba3316dba_-3.png
|
||||
Operation's chunk #4: result_qr_codes/state_sig_proposal_await_participants_confirmations_de09e754-3bc8-4e67-9651-dcdba3316dba_-4.png
|
||||
Operation's chunk #5: result_qr_codes/state_sig_proposal_await_participants_confirmations_de09e754-3bc8-4e67-9651-dcdba3316dba_-5.png
|
||||
Operation's chunk #6: result_qr_codes/state_sig_proposal_await_participants_confirmations_de09e754-3bc8-4e67-9651-dcdba3316dba_-6.png
|
||||
Operation's chunk: result_qr_codes/state_sig_proposal_await_participants_confirmations_de09e754-3bc8-4e67-9651-dcdba3316dba_-0.gif
|
||||
```
|
||||
Open the first response QR code in your default image viewer and take a photo of it. Then go to the node and run:
|
||||
Open the response QR-gif in any gif viewer and take a video of it. Then go to the node and run:
|
||||
```
|
||||
$ ./dc4bc_cli read_from_camera --listen_addr localhost:8080
|
||||
```
|
||||
|
||||
The procedure is the same as with `dc4bc_airgapped`: scan all QR codes until you see a success message:
|
||||
The procedure is the same as with `dc4bc_airgapped`: scan QR-gif until you see a success message:
|
||||
```
|
||||
Operation successfully scanned
|
||||
```
|
||||
|
@ -216,8 +208,25 @@ $ ./dc4bc_cli sign_data AABB10CABB10 dGhlIG1lc3NhZ2UgdG8gc2lnbgo= --listen_addr
|
|||
```
|
||||
Further actions are repetitive and are similar to the DKG procedure. Check for new pending operations, feed them to `dc4bc_airgapped`, pass the responses to the client, then wait for new operations, etc. After some back and forth you'll see the node tell you that the signature is ready:
|
||||
```
|
||||
[john_doe] Handling operation state_signing_partial_signs_collected in airgapped
|
||||
[176 141 250 6 188 93 29 163 218 11 179 113 24 74 95 62 89 163 219 249 135 53 26 212 55 134 143 107 117 216 112 85 163 6 117 153 161 171 235 145 198 253 60 53 22 3 36 84]
|
||||
[john_doe] Handling message with offset 40, type signature_reconstructed
|
||||
Successfully processed message with offset 40, type signature_reconstructed
|
||||
```
|
||||
|
||||
Now you have the full reconstructed signature.
|
||||
Now you have the full reconstructed signature.
|
||||
```
|
||||
./dc4bc_cli get_signatures AABB10CABB10
|
||||
Hash of the signing data: b8fbfd0b1ed86412dd15637967788909
|
||||
DKG round ID: AABB10CABB10
|
||||
Participant: john_doe
|
||||
Reconstructed signature for the data: tK+3CV2CI0flgwWLuhrZA5eaFfuJIvpLAc6CbAy5XBuRpzuCkjOZLCU6z1SvlwQIBJp5dAVa2rtbSy1jl98YtidujVWeUDNUz+kRl2C1C1BeLG5JvzQxhgr2dDxq0thu
|
||||
```
|
||||
It'll show you a list of broadcasted reconstructed signatures for a given DKG round.
|
||||
|
||||
You can verify any signature by executing `verify_signature` command inside the airgapped prompt:
|
||||
```
|
||||
>>> verify_signature
|
||||
> Enter the DKGRoundIdentifier: AABB10CABB10
|
||||
> Enter the BLS signature: tK+3CV2CI0flgwWLuhrZA5eaFfuJIvpLAc6CbAy5XBuRpzuCkjOZLCU6z1SvlwQIBJp5dAVa2rtbSy1jl98YtidujVWeUDNUz+kRl2C1C1BeLG5JvzQxhgr2dDxq0thu
|
||||
> Enter the message which was signed (base64): dGhlIG1lc3NhZ2UgdG8gc2lnbgo=
|
||||
Signature is correct!
|
||||
```
|
|
@ -70,6 +70,8 @@ func (n *Node) storeOperation(t *testing.T, msg storage.Message) {
|
|||
t.Fatalf("failed to unmarshal fsm req: %v", err)
|
||||
}
|
||||
n.partialSigns = append(n.partialSigns, req)
|
||||
case client.SignatureReconstructed:
|
||||
return
|
||||
default:
|
||||
t.Fatalf("invalid event: %s", msg.Event)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package airgapped
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
|
@ -107,7 +106,18 @@ func (am *Machine) reconstructThresholdSignature(o *client.Operation) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("failed to reconsruct full signature for msg: %w", err)
|
||||
}
|
||||
fmt.Println(base64.StdEncoding.EncodeToString(reconstructedSignature))
|
||||
|
||||
response := client.ReconstructedSignature{
|
||||
Data: payload.SrcPayload,
|
||||
Signature: reconstructedSignature,
|
||||
DKGRoundID: o.DKGIdentifier,
|
||||
}
|
||||
respBz, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate reconstructed signature response: %w", err)
|
||||
}
|
||||
o.Event = client.SignatureReconstructed
|
||||
o.ResultMsgs = append(o.ResultMsgs, createMessage(*o, respBz))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/depools/dc4bc/fsm/types/responses"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
@ -135,7 +136,29 @@ func (c *BaseClient) SendMessage(message storage.Message) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// processSignature saves a broadcasted reconstructed signature to a LevelDB
|
||||
func (c *Client) 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)
|
||||
}
|
||||
signature.Participant = message.SenderAddr
|
||||
return c.state.SaveSignature(signature)
|
||||
}
|
||||
|
||||
func (c *BaseClient) ProcessMessage(message storage.Message) error {
|
||||
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
|
||||
}
|
||||
fsmInstance, err := c.getFSMInstance(message.DkgRoundID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to getFSMInstance: %w", err)
|
||||
|
@ -199,6 +222,18 @@ func (c *BaseClient) ProcessMessage(message storage.Message) error {
|
|||
sipf.StateSigningPartialSignsCollected,
|
||||
sipf.StateSigningAwaitConfirmations:
|
||||
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.Addr == c.GetUsername() {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
bz, err := json.Marshal(resp.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal FSM response: %w", err)
|
||||
|
@ -216,6 +251,20 @@ func (c *BaseClient) ProcessMessage(message storage.Message) error {
|
|||
c.Logger.Log("State %s does not require an operation", resp.State)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
if operation != nil {
|
||||
if err := c.state.PutOperation(operation); err != nil {
|
||||
return fmt.Errorf("failed to PutOperation: %w", err)
|
||||
|
@ -237,6 +286,17 @@ func (c *BaseClient) GetOperations() (map[string]*types.Operation, error) {
|
|||
return c.state.GetOperations()
|
||||
}
|
||||
|
||||
//GetSignatures returns all signatures for the given DKG round that were reconstructed on the airgapped machine and
|
||||
// broadcasted by users
|
||||
func (c *Client) 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
|
||||
func (c *Client) GetSignatureByDataHash(dkgID, sigID string) ([]types.ReconstructedSignature, error) {
|
||||
return c.state.GetSignatureByDataHash(dkgID, sigID)
|
||||
}
|
||||
|
||||
// getOperationJSON returns a specific JSON-encoded operation
|
||||
func (c *BaseClient) getOperationJSON(operationID string) ([]byte, error) {
|
||||
operation, err := c.state.GetOperationByID(operationID)
|
||||
|
|
|
@ -117,7 +117,7 @@ func (n *node) run(t *testing.T) {
|
|||
if err = pubKey.UnmarshalBinary(pubKeyReq.MasterKey); err != nil {
|
||||
t.Fatalf("failed to unmarshal pubkey: %v", err)
|
||||
}
|
||||
if err = ioutil.WriteFile(fmt.Sprintf("/tmp/dc4bc_participant_%d.pubkey",
|
||||
if err = ioutil.WriteFile(fmt.Sprintf("/tmp/participant_%d.pubkey",
|
||||
pubKeyReq.ParticipantId), []byte(pubKey.String()), 0666); err != nil {
|
||||
t.Fatalf("failed to write pubkey to temp file: %v", err)
|
||||
}
|
||||
|
@ -196,7 +196,7 @@ func TestFullFlow(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("node %d failed to init client: %v\n", nodeID, err)
|
||||
}
|
||||
airgappedMachine.SetEncryptionKey(clt.GetPubKey()) //just for testing
|
||||
airgappedMachine.SetEncryptionKey([]byte("very_strong_password")) //just for testing
|
||||
if err = airgappedMachine.InitKeys(); err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
|
@ -261,17 +261,7 @@ func TestFullFlow(t *testing.T) {
|
|||
log.Println("Propose message to sign")
|
||||
|
||||
dkgRoundID := md5.Sum(messageDataBz)
|
||||
messageDataSign := requests.SigningProposalStartRequest{
|
||||
ParticipantId: len(nodes) - 1,
|
||||
SrcPayload: []byte("message to sign"),
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
messageDataSignBz, err := json.Marshal(messageDataSign)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal SigningProposalStartRequest: %v\n", err)
|
||||
}
|
||||
|
||||
messageDataBz, err = json.Marshal(map[string][]byte{"data": messageDataSignBz,
|
||||
messageDataBz, err = json.Marshal(map[string][]byte{"data": []byte("message to sign"),
|
||||
"dkgID": dkgRoundID[:]})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal SignatureProposalParticipantsListRequest: %v\n", err)
|
||||
|
|
|
@ -13,7 +13,12 @@ import (
|
|||
"github.com/depools/dc4bc/fsm/fsm"
|
||||
spf "github.com/depools/dc4bc/fsm/state_machines/signature_proposal_fsm"
|
||||
sif "github.com/depools/dc4bc/fsm/state_machines/signing_proposal_fsm"
|
||||
"github.com/depools/dc4bc/fsm/types/requests"
|
||||
"github.com/google/uuid"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/depools/dc4bc/qr"
|
||||
"github.com/depools/dc4bc/storage"
|
||||
|
@ -67,6 +72,9 @@ func (c *BaseClient) StartHTTPServer(listenAddr string) error {
|
|||
mux.HandleFunc("/getOperations", c.getOperationsHandler)
|
||||
mux.HandleFunc("/getOperationQRPath", c.getOperationQRPathHandler)
|
||||
|
||||
mux.HandleFunc("/getSignatures", c.getSignaturesHandler)
|
||||
mux.HandleFunc("/getSignatureByDataHash", c.getSignatureByDataHashHandler)
|
||||
|
||||
mux.HandleFunc("/getOperationQR", c.getOperationQRToBodyHandler)
|
||||
mux.HandleFunc("/handleProcessedOperationJSON", c.handleJSONOperationHandler)
|
||||
mux.HandleFunc("/getOperation", c.getOperationHandler)
|
||||
|
@ -135,6 +143,36 @@ func (c *BaseClient) getOperationsHandler(w http.ResponseWriter, r *http.Request
|
|||
successResponse(w, operations)
|
||||
}
|
||||
|
||||
func (c *Client) getSignaturesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
errorResponse(w, http.StatusBadRequest, "Wrong HTTP method")
|
||||
return
|
||||
}
|
||||
|
||||
signatures, err := c.GetSignatures(r.URL.Query().Get("dkgID"))
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, fmt.Sprintf("failed to get signatures: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
successResponse(w, signatures)
|
||||
}
|
||||
|
||||
func (c *Client) getSignatureByDataHashHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
errorResponse(w, http.StatusBadRequest, "Wrong HTTP method")
|
||||
return
|
||||
}
|
||||
|
||||
signature, err := c.GetSignatureByDataHash(r.URL.Query().Get("dkgID"), r.URL.Query().Get("hash"))
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, fmt.Sprintf("failed to get signature: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
successResponse(w, signature)
|
||||
}
|
||||
|
||||
func (c *BaseClient) getOperationQRPathHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
errorResponse(w, http.StatusBadRequest, "Wrong HTTP method")
|
||||
|
@ -234,9 +272,32 @@ func (c *BaseClient) proposeSignDataHandler(w http.ResponseWriter, r *http.Reque
|
|||
return
|
||||
}
|
||||
|
||||
message, err := c.buildMessage(hex.EncodeToString(req["dkgID"]), sif.EventSigningStart, req["data"])
|
||||
fsmInstance, err := c.getFSMInstance(hex.EncodeToString(req["dkgID"]))
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, fmt.Sprintf("failed to get FSM instance: %v", err))
|
||||
return
|
||||
}
|
||||
participantID, err := fsmInstance.GetIDByAddr(c.GetUsername())
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, fmt.Sprintf("failed to get participantID: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
messageDataSign := requests.SigningProposalStartRequest{
|
||||
ParticipantId: participantID,
|
||||
SrcPayload: req["data"],
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
messageDataSignBz, err := json.Marshal(messageDataSign)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, fmt.Sprintf("failed to marshal SigningProposalStartRequest: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
message, err := c.buildMessage(hex.EncodeToString(req["dkgID"]), sif.EventSigningStart, messageDataSignBz)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, fmt.Sprintf("failed to build message: %v", err))
|
||||
return
|
||||
}
|
||||
if err = c.SendMessage(*message); err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, fmt.Sprintf("failed to send message: %v", err))
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -15,9 +17,10 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
offsetKey = "offset"
|
||||
operationsKey = "operations"
|
||||
fsmStateKey = "fsm_state"
|
||||
offsetKey = "offset"
|
||||
operationsKey = "operations"
|
||||
fsmStateKey = "fsm_state"
|
||||
signaturesKeyPrefix = "signatures"
|
||||
)
|
||||
|
||||
// State is the client's state (it keeps the offset, the FSM state and
|
||||
|
@ -33,6 +36,10 @@ type State interface {
|
|||
DeleteOperation(operationID string) error
|
||||
GetOperations() (map[string]*types.Operation, error)
|
||||
GetOperationByID(operationID string) (*types.Operation, error)
|
||||
|
||||
SaveSignature(signature types.ReconstructedSignature) error
|
||||
GetSignatureByDataHash(dkgID, signatureID string) ([]types.ReconstructedSignature, error)
|
||||
GetSignatures(dkgID string) (map[string][]types.ReconstructedSignature, error)
|
||||
}
|
||||
|
||||
type LevelDBState struct {
|
||||
|
@ -253,3 +260,79 @@ func (s *LevelDBState) getOperations() (map[string]*types.Operation, error) {
|
|||
|
||||
return operations, nil
|
||||
}
|
||||
|
||||
func makeSignatureKey(dkgID string) []byte {
|
||||
return []byte(fmt.Sprintf("%s_%s", signaturesKeyPrefix, dkgID))
|
||||
}
|
||||
|
||||
func (s *LevelDBState) getSignatures(dkgID string) (map[string][]types.ReconstructedSignature, error) {
|
||||
bz, err := s.stateDb.Get(makeSignatureKey(dkgID), nil)
|
||||
if err != nil {
|
||||
if err == leveldb.ErrNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("failed to get signatures for dkgID %s: %w", dkgID, err)
|
||||
}
|
||||
|
||||
var signatures map[string][]types.ReconstructedSignature
|
||||
if err := json.Unmarshal(bz, &signatures); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal Operations: %w", err)
|
||||
}
|
||||
|
||||
return signatures, nil
|
||||
}
|
||||
|
||||
func (s *LevelDBState) GetSignatures(dkgID string) (map[string][]types.ReconstructedSignature, error) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
return s.getSignatures(dkgID)
|
||||
}
|
||||
|
||||
func (s *LevelDBState) GetSignatureByDataHash(dkgID, signatureID string) ([]types.ReconstructedSignature, error) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
signatures, err := s.getSignatures(dkgID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to getSignatures: %w", err)
|
||||
}
|
||||
|
||||
signature, ok := signatures[signatureID]
|
||||
if !ok {
|
||||
return nil, errors.New("signature not found")
|
||||
}
|
||||
|
||||
return signature, nil
|
||||
}
|
||||
|
||||
func (s *LevelDBState) SaveSignature(signature types.ReconstructedSignature) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
signatures, err := s.getSignatures(signature.DKGRoundID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to getSignatures: %w", err)
|
||||
}
|
||||
if signatures == nil {
|
||||
signatures = make(map[string][]types.ReconstructedSignature)
|
||||
}
|
||||
|
||||
dataHash := md5.Sum(signature.Data)
|
||||
dataHashString := hex.EncodeToString(dataHash[:])
|
||||
|
||||
sig := signatures[dataHashString]
|
||||
sig = append(sig, signature)
|
||||
signatures[dataHashString] = sig
|
||||
|
||||
signaturesJSON, err := json.Marshal(signatures)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal signatures: %w", err)
|
||||
}
|
||||
|
||||
if err := s.stateDb.Put(makeSignatureKey(signature.DKGRoundID), signaturesJSON, nil); err != nil {
|
||||
return fmt.Errorf("failed to save signatures: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -18,9 +18,17 @@ import (
|
|||
type OperationType string
|
||||
|
||||
const (
|
||||
DKGCommits OperationType = "dkg_commits"
|
||||
DKGCommits OperationType = "dkg_commits"
|
||||
SignatureReconstructed fsm.Event = "signature_reconstructed"
|
||||
)
|
||||
|
||||
type ReconstructedSignature struct {
|
||||
Data []byte
|
||||
Signature []byte
|
||||
Participant string
|
||||
DKGRoundID string
|
||||
}
|
||||
|
||||
// Operation is the type for any Operation that might be required for
|
||||
// both DKG and signing process (e.g.,
|
||||
type Operation struct {
|
||||
|
|
|
@ -54,6 +54,8 @@ func main() {
|
|||
getUsernameCommand(),
|
||||
getPubKeyCommand(),
|
||||
getHashOfStartDKGCommand(),
|
||||
getSignaturesCommand(),
|
||||
getSignatureCommand(),
|
||||
)
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
log.Fatalf("Failed to execute root command: %v", err)
|
||||
|
@ -120,6 +122,100 @@ func getOperationsCommand() *cobra.Command {
|
|||
}
|
||||
}
|
||||
|
||||
func getSignaturesRequest(host string, dkgID string) (*SignaturesResponse, error) {
|
||||
resp, err := http.Get(fmt.Sprintf("http://%s/getSignatures?dkgID=%s", host, dkgID))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get signatures: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
responseBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read body: %w", err)
|
||||
}
|
||||
|
||||
var response SignaturesResponse
|
||||
if err = json.Unmarshal(responseBody, &response); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal response: %v", err)
|
||||
}
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
func getSignaturesCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "get_signatures [dkgID]",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Short: "returns all signatures for the given DKG round that were reconstructed on the airgapped machine",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
listenAddr, err := cmd.Flags().GetString(flagListenAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read configuration: %v", err)
|
||||
}
|
||||
signatures, err := getSignaturesRequest(listenAddr, args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get signatures: %w", err)
|
||||
}
|
||||
if signatures.ErrorMessage != "" {
|
||||
return fmt.Errorf("failed to get signatures: %s", signatures.ErrorMessage)
|
||||
}
|
||||
for dataHash, signature := range signatures.Result {
|
||||
fmt.Printf("Hash of the signing data: %s\n", dataHash)
|
||||
for _, participantSig := range signature {
|
||||
fmt.Printf("\tDKG round ID: %s\n", participantSig.DKGRoundID)
|
||||
fmt.Printf("\tParticipant: %s\n", participantSig.Participant)
|
||||
fmt.Printf("\tReconstructed signature for the data: %s\n", base64.StdEncoding.EncodeToString(participantSig.Signature))
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getSignatureRequest(host string, dkgID, dataHash string) (*SignatureResponse, error) {
|
||||
resp, err := http.Get(fmt.Sprintf("http://%s/getSignatureByDataHash?dkgID=%s&hash=%s", host, dkgID, dataHash))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get signatures: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
responseBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read body: %w", err)
|
||||
}
|
||||
|
||||
var response SignatureResponse
|
||||
if err = json.Unmarshal(responseBody, &response); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal response: %v", err)
|
||||
}
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
func getSignatureCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "get_signature [dkgID] [hash_of_the_signed_data]",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Short: "returns a list of reconstructed signatures of the signed data broadcasted by users",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
listenAddr, err := cmd.Flags().GetString(flagListenAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read configuration: %v", err)
|
||||
}
|
||||
signatures, err := getSignatureRequest(listenAddr, args[0], args[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get signatures: %w", err)
|
||||
}
|
||||
if signatures.ErrorMessage != "" {
|
||||
return fmt.Errorf("failed to get signatures: %s", signatures.ErrorMessage)
|
||||
}
|
||||
for _, participantSig := range signatures.Result {
|
||||
fmt.Printf("\tParticipant: %s\n", participantSig.Participant)
|
||||
fmt.Printf("\tReconstructed signature for the data: %s\n", base64.StdEncoding.EncodeToString(participantSig.Signature))
|
||||
fmt.Println()
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getOperationRequest(host string, operationID string) (*OperationResponse, error) {
|
||||
resp, err := http.Get(fmt.Sprintf("http://%s/getOperation?operationID=%s", host, operationID))
|
||||
if err != nil {
|
||||
|
@ -385,9 +481,9 @@ func getHashOfStartDKGCommand() *cobra.Command {
|
|||
|
||||
func proposeSignMessageCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "sign_data [dkg_id] [data]",
|
||||
Use: "sign_data [dkg_id] [file_path]",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Short: "sends a propose message to sign the data",
|
||||
Short: "sends a propose message to sign the data in the file",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
listenAddr, err := cmd.Flags().GetString(flagListenAddr)
|
||||
if err != nil {
|
||||
|
@ -399,22 +495,12 @@ func proposeSignMessageCommand() *cobra.Command {
|
|||
return fmt.Errorf("failed to decode dkgID: %w", err)
|
||||
}
|
||||
|
||||
data, err := base64.StdEncoding.DecodeString(args[1])
|
||||
data, err := ioutil.ReadFile(args[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode data")
|
||||
return fmt.Errorf("failed to read the file")
|
||||
}
|
||||
|
||||
messageDataSign := requests.SigningProposalStartRequest{
|
||||
ParticipantId: 0, //TODO: determine participantID
|
||||
SrcPayload: data,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
messageDataSignBz, err := json.Marshal(messageDataSign)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal SigningProposalStartRequest: %v", err)
|
||||
}
|
||||
|
||||
messageDataBz, err := json.Marshal(map[string][]byte{"data": messageDataSignBz,
|
||||
messageDataBz, err := json.Marshal(map[string][]byte{"data": data,
|
||||
"dkgID": dkgID})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal SigningProposalStartRequest: %v", err)
|
||||
|
|
|
@ -32,6 +32,16 @@ type OperationsResponse struct {
|
|||
Result map[string]*types.Operation `json:"result"`
|
||||
}
|
||||
|
||||
type SignaturesResponse struct {
|
||||
ErrorMessage string `json:"error_message,omitempty"`
|
||||
Result map[string][]types.ReconstructedSignature `json:"result"`
|
||||
}
|
||||
|
||||
type SignatureResponse struct {
|
||||
ErrorMessage string `json:"error_message,omitempty"`
|
||||
Result []types.ReconstructedSignature `json:"result"`
|
||||
}
|
||||
|
||||
type OperationResponse struct {
|
||||
ErrorMessage string `json:"error_message,omitempty"`
|
||||
Result []byte `json:"result"`
|
||||
|
|
|
@ -21,6 +21,7 @@ type DumpedMachineStatePayload struct {
|
|||
DKGProposalPayload *DKGConfirmation
|
||||
SigningProposalPayload *SigningConfirmation
|
||||
PubKeys map[string]ed25519.PublicKey
|
||||
IDs map[string]int
|
||||
}
|
||||
|
||||
// Signature quorum
|
||||
|
@ -123,6 +124,13 @@ func (p *DumpedMachineStatePayload) SetPubKeyAddr(addr string, pubKey ed25519.Pu
|
|||
p.PubKeys[addr] = pubKey
|
||||
}
|
||||
|
||||
func (p *DumpedMachineStatePayload) SetIDAddr(addr string, id int) {
|
||||
if p.IDs == nil {
|
||||
p.IDs = make(map[string]int)
|
||||
}
|
||||
p.IDs[addr] = id
|
||||
}
|
||||
|
||||
func (p *DumpedMachineStatePayload) GetPubKeyByAddr(addr string) (ed25519.PublicKey, error) {
|
||||
if p.PubKeys == nil {
|
||||
return nil, errors.New("{PubKeys} not initialized")
|
||||
|
@ -137,3 +145,17 @@ func (p *DumpedMachineStatePayload) GetPubKeyByAddr(addr string) (ed25519.Public
|
|||
|
||||
return pubKey, nil
|
||||
}
|
||||
|
||||
func (p *DumpedMachineStatePayload) GetIDByAddr(addr string) (int, error) {
|
||||
if p.IDs == nil {
|
||||
return -1, errors.New("{IDs} not initialized")
|
||||
}
|
||||
if addr == "" {
|
||||
return -1, errors.New("{addr} cannot be empty")
|
||||
}
|
||||
id, ok := p.IDs[addr]
|
||||
if !ok {
|
||||
return -1, errors.New("cannot find id by {addr}")
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
|
|
@ -94,6 +94,22 @@ func (i *FSMInstance) GetPubKeyByAddr(addr string) (ed25519.PublicKey, error) {
|
|||
return i.dump.Payload.GetPubKeyByAddr(addr)
|
||||
}
|
||||
|
||||
func (i *FSMInstance) SigningQuorumGetParticipant(id int) (*internal.SigningProposalParticipant, error) {
|
||||
if i.dump == nil {
|
||||
return nil, errors.New("dump not initialized")
|
||||
}
|
||||
|
||||
return i.dump.Payload.SigningQuorumGet(id), nil
|
||||
}
|
||||
|
||||
func (i *FSMInstance) GetIDByAddr(addr string) (int, error) {
|
||||
if i.dump == nil {
|
||||
return -1, errors.New("dump not initialized")
|
||||
}
|
||||
|
||||
return i.dump.Payload.GetIDByAddr(addr)
|
||||
}
|
||||
|
||||
func (i *FSMInstance) Do(event fsm.Event, args ...interface{}) (result *fsm.Response, dump []byte, err error) {
|
||||
var dumpErr error
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ func (m *SignatureProposalFSM) actionInitSignatureProposal(inEvent fsm.Event, ar
|
|||
}
|
||||
|
||||
m.payload.SetPubKeyAddr(participant.Addr, participant.PubKey)
|
||||
m.payload.SetIDAddr(participant.Addr, index)
|
||||
}
|
||||
|
||||
// Checking fo quorum length
|
||||
|
|
|
@ -150,3 +150,47 @@ func (mr *MockStateMockRecorder) GetOperationByID(operationID interface{}) *gomo
|
|||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOperationByID", reflect.TypeOf((*MockState)(nil).GetOperationByID), operationID)
|
||||
}
|
||||
|
||||
// SaveSignature mocks base method
|
||||
func (m *MockState) SaveSignature(signature types.ReconstructedSignature) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SaveSignature", signature)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SaveSignature indicates an expected call of SaveSignature
|
||||
func (mr *MockStateMockRecorder) SaveSignature(signature interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveSignature", reflect.TypeOf((*MockState)(nil).SaveSignature), signature)
|
||||
}
|
||||
|
||||
// GetSignatureByDataHash mocks base method
|
||||
func (m *MockState) GetSignatureByDataHash(dkgID, signatureID string) ([]types.ReconstructedSignature, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetSignatureByDataHash", dkgID, signatureID)
|
||||
ret0, _ := ret[0].([]types.ReconstructedSignature)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetSignatureByDataHash indicates an expected call of GetSignatureByDataHash
|
||||
func (mr *MockStateMockRecorder) GetSignatureByDataHash(dkgID, signatureID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSignatureByDataHash", reflect.TypeOf((*MockState)(nil).GetSignatureByDataHash), dkgID, signatureID)
|
||||
}
|
||||
|
||||
// GetSignatures mocks base method
|
||||
func (m *MockState) GetSignatures(dkgID string) (map[string][]types.ReconstructedSignature, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetSignatures", dkgID)
|
||||
ret0, _ := ret[0].(map[string][]types.ReconstructedSignature)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetSignatures indicates an expected call of GetSignatures
|
||||
func (mr *MockStateMockRecorder) GetSignatures(dkgID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSignatures", reflect.TypeOf((*MockState)(nil).GetSignatures), dkgID)
|
||||
}
|
||||
|
|
|
@ -32,12 +32,6 @@ func (m *MockProcessor) EXPECT() *MockProcessorMockRecorder {
|
|||
return m.recorder
|
||||
}
|
||||
|
||||
func (m *MockProcessor) SetDelay(int) {
|
||||
}
|
||||
|
||||
func (m *MockProcessor) SetChunkSize(int) {
|
||||
}
|
||||
|
||||
// ReadQR mocks base method
|
||||
func (m *MockProcessor) ReadQR() ([]byte, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
@ -66,3 +60,27 @@ func (mr *MockProcessorMockRecorder) WriteQR(path, data interface{}) *gomock.Cal
|
|||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteQR", reflect.TypeOf((*MockProcessor)(nil).WriteQR), path, data)
|
||||
}
|
||||
|
||||
// SetDelay mocks base method
|
||||
func (m *MockProcessor) SetDelay(delay int) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "SetDelay", delay)
|
||||
}
|
||||
|
||||
// SetDelay indicates an expected call of SetDelay
|
||||
func (mr *MockProcessorMockRecorder) SetDelay(delay interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDelay", reflect.TypeOf((*MockProcessor)(nil).SetDelay), delay)
|
||||
}
|
||||
|
||||
// SetChunkSize mocks base method
|
||||
func (m *MockProcessor) SetChunkSize(chunkSize int) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "SetChunkSize", chunkSize)
|
||||
}
|
||||
|
||||
// SetChunkSize indicates an expected call of SetChunkSize
|
||||
func (mr *MockProcessorMockRecorder) SetChunkSize(chunkSize interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetChunkSize", reflect.TypeOf((*MockProcessor)(nil).SetChunkSize), chunkSize)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue