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 }