github.com/decred/politeia@v1.4.0/politeiad/backend/gitbe/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 gitbe 6 7 import ( 8 "bytes" 9 "crypto/sha256" 10 "crypto/tls" 11 "encoding/hex" 12 "encoding/json" 13 "errors" 14 "fmt" 15 "io" 16 "net/http" 17 "time" 18 19 v1 "github.com/decred/dcrtime/api/v1" 20 "github.com/decred/dcrtime/merkle" 21 ) 22 23 var ( 24 skipVerify = false 25 httpClient = &http.Client{ 26 Timeout: 1 * time.Minute, 27 Transport: &http.Transport{ 28 IdleConnTimeout: 1 * time.Minute, 29 ResponseHeaderTimeout: 1 * time.Minute, 30 TLSClientConfig: &tls.Config{ 31 InsecureSkipVerify: skipVerify, 32 }, 33 }, 34 } 35 ) 36 37 type errNotAnchored struct { 38 err error 39 } 40 41 func (e errNotAnchored) Error() string { 42 return e.err.Error() 43 } 44 45 // isDigest determines if a string is a valid SHA256 digest. 46 func isDigest(digest string) bool { 47 return v1.RegexpSHA256.MatchString(digest) 48 } 49 50 // getError returns the error that is embedded in a JSON reply. 51 func getError(r io.Reader) (string, error) { 52 var e interface{} 53 decoder := json.NewDecoder(r) 54 if err := decoder.Decode(&e); err != nil { 55 return "", err 56 } 57 m, ok := e.(map[string]interface{}) 58 if !ok { 59 return "", fmt.Errorf("Could not decode response") 60 } 61 rError, ok := m["error"] 62 if !ok { 63 return "", fmt.Errorf("No error response") 64 } 65 return fmt.Sprintf("%v", rError), nil 66 } 67 68 // timestamp sends a Timestamp request to the provided host. The caller is 69 // responsible for assembling the host string based on what net to use. 70 func timestamp(id, host string, digests []*[sha256.Size]byte) error { 71 // batch uploads 72 ts := v1.Timestamp{ 73 ID: id, 74 Digests: make([]string, 0, len(digests)), 75 } 76 for _, digest := range digests { 77 ts.Digests = append(ts.Digests, hex.EncodeToString(digest[:])) 78 } 79 b, err := json.Marshal(ts) 80 if err != nil { 81 return err 82 } 83 84 r, err := httpClient.Post(host+v1.TimestampRoute, "application/json", 85 bytes.NewReader(b)) 86 if err != nil { 87 return err 88 } 89 defer r.Body.Close() 90 91 if r.StatusCode != http.StatusOK { 92 e, err := getError(r.Body) 93 if err != nil { 94 return fmt.Errorf("%v", r.Status) 95 } 96 return fmt.Errorf("%v: %v", r.Status, e) 97 } 98 99 // Decode response. 100 var tsReply v1.TimestampReply 101 decoder := json.NewDecoder(r.Body) 102 if err := decoder.Decode(&tsReply); err != nil { 103 return fmt.Errorf("Could node decode TimestampReply: %v", err) 104 } 105 106 for i, result := range tsReply.Results { 107 if result == v1.ResultExistsError { 108 // Been alread anchored so ignore. 109 continue 110 } 111 if result != v1.ResultOK { 112 msg, ok := v1.Result[result] 113 if !ok { 114 msg = "UNKNOWN ERROR" 115 } 116 return fmt.Errorf("anchor (%v): %v", i, msg) 117 } 118 } 119 120 return nil 121 } 122 123 // verifyTimestamp sends a dcrtime Verify command to the provided host. It 124 // checks and validates the entire reply. A single failure is considered 125 // terminal and an error is returned. If the reply is valid it is returned to 126 // the caller for further processing. This means that the caller can be 127 // assured that all checks have been done and the data is readily usable. 128 // 129 // Note the Result in the reply will be set to OK as soon as the digest is 130 // waiting to be anchored. The ChainInformation will be populated once the 131 // digest has been included in a dcr transaction, except for the ChainTimestamp 132 // field. The ChainTimestamp field is only populated once the dcr transaction 133 // has 6 confirmations. 134 func verifyTimestamp(id, host string, digests []string) (*v1.VerifyReply, error) { 135 ver := v1.Verify{ 136 ID: id, 137 } 138 139 for _, digest := range digests { 140 if isDigest(digest) { 141 ver.Digests = append(ver.Digests, digest) 142 continue 143 } 144 145 return nil, fmt.Errorf("not a valid digest: %v", digest) 146 } 147 148 // Convert Verify to JSON 149 b, err := json.Marshal(ver) 150 if err != nil { 151 return nil, err 152 } 153 154 r, err := httpClient.Post(host+v1.VerifyRoute, "application/json", 155 bytes.NewReader(b)) 156 if err != nil { 157 return nil, err 158 } 159 defer r.Body.Close() 160 161 if r.StatusCode != http.StatusOK { 162 e, err := getError(r.Body) 163 if err != nil { 164 return nil, fmt.Errorf("%v", r.Status) 165 } 166 return nil, fmt.Errorf("%v: %v", r.Status, e) 167 } 168 169 // Decode response. 170 var vr v1.VerifyReply 171 decoder := json.NewDecoder(r.Body) 172 if err := decoder.Decode(&vr); err != nil { 173 return nil, fmt.Errorf("Could node decode VerifyReply: %v", err) 174 } 175 176 for _, v := range vr.Digests { 177 if v.Result != v1.ResultOK { 178 // We only report not found since the other errors are 179 // not applicable. 180 if v.Result == v1.ResultDoesntExistError { 181 return nil, fmt.Errorf("Digest not found: %v", 182 v.Digest) 183 } 184 continue 185 } 186 _, ok := v1.Result[v.Result] 187 if !ok { 188 return nil, fmt.Errorf("%v invalid error code %v", 189 v.Digest, v.Result) 190 } 191 192 // Verify merkle path. 193 root, err := merkle.VerifyAuthPath(&v.ChainInformation.MerklePath) 194 if err != nil { 195 if !errors.Is(err, merkle.ErrEmpty) { 196 return nil, fmt.Errorf("%v invalid auth path "+ 197 "%v", v.Digest, err) 198 } 199 return nil, errNotAnchored{ 200 err: fmt.Errorf("%v Not anchored", v.Digest), 201 } 202 } 203 204 // Verify merkle root. 205 merkleRoot, err := hex.DecodeString(v.ChainInformation.MerkleRoot) 206 if err != nil { 207 return nil, fmt.Errorf("invalid merkle root: %v", err) 208 } 209 // This is silly since we check against returned root. 210 if !bytes.Equal(root[:], merkleRoot) { 211 return nil, fmt.Errorf("%v invalid merkle root", 212 v.Digest) 213 } 214 215 // All good 216 } 217 218 return &vr, nil 219 }