github.com/decred/politeia@v1.4.0/politeiad/backendv2/tstorebe/tstore/dcrtime.go (about)

     1  // Copyright (c) 2020-2021 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package tstore
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/hex"
    10  	"encoding/json"
    11  	"errors"
    12  	"fmt"
    13  	"net/http"
    14  
    15  	dcrtime "github.com/decred/dcrtime/api/v2"
    16  	"github.com/decred/dcrtime/merkle"
    17  	"github.com/decred/politeia/util"
    18  )
    19  
    20  // dcrtimeClient is a client for interacting with the dcrtime API.
    21  type dcrtimeClient struct {
    22  	host     string
    23  	certPath string
    24  	http     *http.Client
    25  }
    26  
    27  // isDigestSHA256 returns whether the provided digest is a valid SHA256 digest.
    28  func isDigestSHA256(digest string) bool {
    29  	return dcrtime.RegexpSHA256.MatchString(digest)
    30  }
    31  
    32  // makeReq makes an http request to a dcrtime method and route, serializing the
    33  // provided object as the request body. The response body is returned as a byte
    34  // slice.
    35  func (c *dcrtimeClient) makeReq(method string, route string, v interface{}) ([]byte, error) {
    36  	var (
    37  		reqBody []byte
    38  		err     error
    39  	)
    40  	if v != nil {
    41  		reqBody, err = json.Marshal(v)
    42  		if err != nil {
    43  			return nil, err
    44  		}
    45  	}
    46  
    47  	fullRoute := c.host + route
    48  
    49  	log.Tracef("%v %v", method, fullRoute)
    50  
    51  	req, err := http.NewRequest(method, fullRoute, bytes.NewReader(reqBody))
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	r, err := c.http.Do(req)
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  	defer r.Body.Close()
    61  
    62  	if r.StatusCode != http.StatusOK {
    63  		e, err := util.GetErrorFromJSON(r.Body)
    64  		if err != nil {
    65  			return nil, fmt.Errorf("%v", r.Status)
    66  		}
    67  		return nil, fmt.Errorf("%v: %v", r.Status, e)
    68  	}
    69  
    70  	return util.RespBody(r), nil
    71  }
    72  
    73  // timestampBatch posts digests to the dcrtime v2 batch timestamp route.
    74  func (c *dcrtimeClient) timestampBatch(id string, digests []string) (*dcrtime.TimestampBatchReply, error) {
    75  	log.Tracef("timestampBatch: %v %v", id, digests)
    76  
    77  	// Setup request
    78  	for _, v := range digests {
    79  		if !isDigestSHA256(v) {
    80  			return nil, fmt.Errorf("invalid digest: %v", v)
    81  		}
    82  	}
    83  	tb := dcrtime.TimestampBatch{
    84  		ID:      id,
    85  		Digests: digests,
    86  	}
    87  
    88  	// Send request
    89  	respBody, err := c.makeReq(http.MethodPost, dcrtime.TimestampBatchRoute, tb)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	// Decode reply
    95  	var tbr dcrtime.TimestampBatchReply
    96  	err = json.Unmarshal(respBody, &tbr)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	return &tbr, nil
   102  }
   103  
   104  // verifyBatch returns the data to verify that a digest was included in a
   105  // dcrtime timestamp. This function verifies the merkle path and merkle root of
   106  // all successful timestamps. The caller is responsible for checking the result
   107  // code and handling digests that failed to be timestamped.
   108  //
   109  // Note the Result in the reply will be set to OK as soon as the digest is
   110  // waiting to be anchored. All the ChainInformation fields will be populated
   111  // once the digest has been included in a dcr transaction, except for the
   112  // ChainTimestamp field. The ChainTimestamp field is only populated once the
   113  // dcr transaction has 6 confirmations.
   114  func (c *dcrtimeClient) verifyBatch(id string, digests []string) (*dcrtime.VerifyBatchReply, error) {
   115  	log.Tracef("verifyBatch: %v %v", id, digests)
   116  
   117  	// Setup request
   118  	for _, v := range digests {
   119  		if !isDigestSHA256(v) {
   120  			return nil, fmt.Errorf("invalid digest: %v", v)
   121  		}
   122  	}
   123  	vb := dcrtime.VerifyBatch{
   124  		ID:      id,
   125  		Digests: digests,
   126  	}
   127  
   128  	// Send request
   129  	respBody, err := c.makeReq(http.MethodPost, dcrtime.VerifyBatchRoute, vb)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  
   134  	// Decode reply
   135  	var vbr dcrtime.VerifyBatchReply
   136  	err = json.Unmarshal(respBody, &vbr)
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  
   141  	// Verify the merkle path and the merkle root of the timestamps
   142  	// that were successful. The caller is responsible for handling
   143  	// the digests that failed be timestamped.
   144  	for _, v := range vbr.Digests {
   145  		if v.Result != dcrtime.ResultOK {
   146  			// Nothing to verify
   147  			continue
   148  		}
   149  
   150  		// Verify merkle path
   151  		root, err := merkle.VerifyAuthPath(&v.ChainInformation.MerklePath)
   152  		if err != nil {
   153  			if errors.Is(err, merkle.ErrEmpty) {
   154  				// A dcr transaction has not been sent yet so there is
   155  				// nothing to verify.
   156  				continue
   157  			}
   158  			return nil, fmt.Errorf("VerifyAuthPath %v: %v", v.Digest, err)
   159  		}
   160  
   161  		// Verify merkle root
   162  		merkleRoot, err := hex.DecodeString(v.ChainInformation.MerkleRoot)
   163  		if err != nil {
   164  			return nil, fmt.Errorf("invalid merkle root: %v", err)
   165  		}
   166  		if !bytes.Equal(merkleRoot, root[:]) {
   167  			return nil, fmt.Errorf("invalid merkle root %v: got %x, want %x",
   168  				v.Digest, merkleRoot, root[:])
   169  		}
   170  	}
   171  
   172  	return &vbr, nil
   173  }
   174  
   175  // newDcrtimeClient returns a new dcrtimeClient.
   176  func newDcrtimeClient(host, certPath string) (*dcrtimeClient, error) {
   177  	c, err := util.NewHTTPClient(false, certPath)
   178  	if err != nil {
   179  		return nil, err
   180  	}
   181  	return &dcrtimeClient{
   182  		host:     host,
   183  		certPath: certPath,
   184  		http:     c,
   185  	}, nil
   186  }