github.com/decred/politeia@v1.4.0/politeiawww/client/comments.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 client 6 7 import ( 8 "encoding/json" 9 "fmt" 10 "net/http" 11 "strconv" 12 13 backend "github.com/decred/politeia/politeiad/backendv2" 14 cmv1 "github.com/decred/politeia/politeiawww/api/comments/v1" 15 "github.com/decred/politeia/util" 16 ) 17 18 // CommentPolicy sends a comments v1 Policy request to politeiawww. 19 func (c *Client) CommentPolicy() (*cmv1.PolicyReply, error) { 20 resBody, err := c.makeReq(http.MethodPost, 21 cmv1.APIRoute, cmv1.RoutePolicy, nil) 22 if err != nil { 23 return nil, err 24 } 25 26 var pr cmv1.PolicyReply 27 err = json.Unmarshal(resBody, &pr) 28 if err != nil { 29 return nil, err 30 } 31 32 return &pr, nil 33 } 34 35 // CommentNew sends a comments v1 New request to politeiawww. 36 func (c *Client) CommentNew(n cmv1.New) (*cmv1.NewReply, error) { 37 resBody, err := c.makeReq(http.MethodPost, 38 cmv1.APIRoute, cmv1.RouteNew, n) 39 if err != nil { 40 return nil, err 41 } 42 43 var nr cmv1.NewReply 44 err = json.Unmarshal(resBody, &nr) 45 if err != nil { 46 return nil, err 47 } 48 49 return &nr, nil 50 } 51 52 // CommentEdit sends a comments v1 Edit request to politeiawww. 53 func (c *Client) CommentEdit(e cmv1.Edit) (*cmv1.EditReply, error) { 54 resBody, err := c.makeReq(http.MethodPost, 55 cmv1.APIRoute, cmv1.RouteEdit, e) 56 if err != nil { 57 return nil, err 58 } 59 60 var er cmv1.EditReply 61 err = json.Unmarshal(resBody, &er) 62 if err != nil { 63 return nil, err 64 } 65 66 return &er, nil 67 } 68 69 // CommentVote sends a comments v1 Vote request to politeiawww. 70 func (c *Client) CommentVote(v cmv1.Vote) (*cmv1.VoteReply, error) { 71 resBody, err := c.makeReq(http.MethodPost, 72 cmv1.APIRoute, cmv1.RouteVote, v) 73 if err != nil { 74 return nil, err 75 } 76 77 var vr cmv1.VoteReply 78 err = json.Unmarshal(resBody, &vr) 79 if err != nil { 80 return nil, err 81 } 82 83 return &vr, nil 84 } 85 86 // CommentDel sends a comments v1 Del request to politeiawww. 87 func (c *Client) CommentDel(d cmv1.Del) (*cmv1.DelReply, error) { 88 resBody, err := c.makeReq(http.MethodPost, 89 cmv1.APIRoute, cmv1.RouteDel, d) 90 if err != nil { 91 return nil, err 92 } 93 94 var dr cmv1.DelReply 95 err = json.Unmarshal(resBody, &dr) 96 if err != nil { 97 return nil, err 98 } 99 100 return &dr, nil 101 } 102 103 // CommentCount sends a comments v1 Count request to politeiawww. 104 func (c *Client) CommentCount(cc cmv1.Count) (*cmv1.CountReply, error) { 105 resBody, err := c.makeReq(http.MethodPost, 106 cmv1.APIRoute, cmv1.RouteCount, cc) 107 if err != nil { 108 return nil, err 109 } 110 111 var cr cmv1.CountReply 112 err = json.Unmarshal(resBody, &cr) 113 if err != nil { 114 return nil, err 115 } 116 117 return &cr, nil 118 } 119 120 // Comments sends a comments v1 Comments request to politeiawww. 121 func (c *Client) Comments(cm cmv1.Comments) (*cmv1.CommentsReply, error) { 122 resBody, err := c.makeReq(http.MethodPost, 123 cmv1.APIRoute, cmv1.RouteComments, cm) 124 if err != nil { 125 return nil, err 126 } 127 128 var cr cmv1.CommentsReply 129 err = json.Unmarshal(resBody, &cr) 130 if err != nil { 131 return nil, err 132 } 133 134 return &cr, nil 135 } 136 137 // CommentVotes sends a comments v1 Votes request to politeiawww. 138 func (c *Client) CommentVotes(v cmv1.Votes) (*cmv1.VotesReply, error) { 139 resBody, err := c.makeReq(http.MethodPost, 140 cmv1.APIRoute, cmv1.RouteVotes, v) 141 if err != nil { 142 return nil, err 143 } 144 145 var vr cmv1.VotesReply 146 err = json.Unmarshal(resBody, &vr) 147 if err != nil { 148 return nil, err 149 } 150 151 return &vr, nil 152 } 153 154 // CommentTimestamps sends a comments v1 Timestamps request to politeiawww. 155 func (c *Client) CommentTimestamps(t cmv1.Timestamps) (*cmv1.TimestampsReply, error) { 156 resBody, err := c.makeReq(http.MethodPost, 157 cmv1.APIRoute, cmv1.RouteTimestamps, t) 158 if err != nil { 159 return nil, err 160 } 161 162 var tr cmv1.TimestampsReply 163 err = json.Unmarshal(resBody, &tr) 164 if err != nil { 165 return nil, err 166 } 167 168 return &tr, nil 169 } 170 171 // commentDelVerify verifies the signature of a comment that has been deleted. 172 // The signature will be from the deletion event, not the original comment 173 // submission. 174 func commentDelVerify(c cmv1.Comment, serverPublicKey string) error { 175 if !c.Deleted { 176 return fmt.Errorf("not a deleted comment") 177 } 178 179 // Verify delete action. The deletion signature is of the 180 // State+Token+CommentID+Reason. 181 msg := strconv.FormatUint(uint64(c.State), 10) + c.Token + 182 strconv.FormatUint(uint64(c.CommentID), 10) + c.Reason 183 err := util.VerifySignature(c.Signature, c.PublicKey, msg) 184 if err != nil { 185 return fmt.Errorf("unable to verify comment %v del signature: %v", 186 c.CommentID, err) 187 } 188 189 // Verify receipt. Receipt is the server signature of the client 190 // signature. 191 err = util.VerifySignature(c.Receipt, serverPublicKey, c.Signature) 192 if err != nil { 193 return fmt.Errorf("unable to verify comment %v receipt: %v", 194 c.CommentID, err) 195 } 196 197 return nil 198 } 199 200 // CommentEditVerify verifies the edited comment signature and receipt. 201 func CommentEditVerify(c cmv1.Comment, serverPublicKey string) error { 202 // Verify comment. The signature is the client signature of the: 203 // State + Token + ParentID + CommentID + Comment + 204 // ExtraData + ExtraDataHint. 205 msg := strconv.FormatUint(uint64(c.State), 10) + c.Token + 206 strconv.FormatUint(uint64(c.ParentID), 10) + 207 strconv.FormatUint(uint64(c.CommentID), 10) + 208 c.Comment + c.ExtraData + c.ExtraDataHint 209 err := util.VerifySignature(c.Signature, c.PublicKey, msg) 210 if err != nil { 211 return fmt.Errorf("unable to verify edited comment %v signature: %v", 212 c.CommentID, err) 213 } 214 215 // Verify receipt. The receipt is the server signature of the 216 // client signature. 217 err = util.VerifySignature(c.Receipt, serverPublicKey, c.Signature) 218 if err != nil { 219 return fmt.Errorf("unable to verify edited comment %v receipt: %v", 220 c.CommentID, err) 221 } 222 223 return nil 224 } 225 226 // CommentVerify verifies the comment signature and receipt. If the comment 227 // has been deleted then the deletion signature and receipt will be verified. 228 func CommentVerify(c cmv1.Comment, serverPublicKey string) error { 229 if c.Deleted { 230 return commentDelVerify(c, serverPublicKey) 231 } 232 233 // Verify comment. The signature is the client signature of the 234 // State + Token + ParentID + Comment + ExtraData + ExtraDataHint. 235 msg := strconv.FormatUint(uint64(c.State), 10) + c.Token + 236 strconv.FormatUint(uint64(c.ParentID), 10) + c.Comment + 237 c.ExtraData + c.ExtraDataHint 238 err := util.VerifySignature(c.Signature, c.PublicKey, msg) 239 if err != nil { 240 return fmt.Errorf("unable to verify comment %v signature: %v", 241 c.CommentID, err) 242 } 243 244 // Verify receipt. The receipt is the server signature of the 245 // client signature. 246 err = util.VerifySignature(c.Receipt, serverPublicKey, c.Signature) 247 if err != nil { 248 return fmt.Errorf("unable to verify comment %v receipt: %v", 249 c.CommentID, err) 250 } 251 252 return nil 253 } 254 255 // CommentTimestampVerify verifies that all timestamps in the provided 256 // CommentTimestamp are valid. 257 func CommentTimestampVerify(ct cmv1.CommentTimestamp) error { 258 // Verify comment adds 259 for i, ts := range ct.Adds { 260 err := backend.VerifyTimestamp(convertCommentTimestamp(ts)) 261 if err != nil { 262 if err == backend.ErrNotTimestamped { 263 return err 264 } 265 return fmt.Errorf("verify comment add timestamp %v: %v", i, err) 266 } 267 } 268 269 // Verify comment del if one exists 270 if ct.Del == nil { 271 return nil 272 } 273 err := backend.VerifyTimestamp(convertCommentTimestamp(*ct.Del)) 274 if err != nil { 275 if err == backend.ErrNotTimestamped { 276 return err 277 } 278 return fmt.Errorf("verify comment del timestamp: %v", err) 279 } 280 281 return nil 282 } 283 284 // CommentTimestampsVerify verifies that all timestamps in a comments v1 285 // TimestampsReply are valid. The IDs of comments that have not been anchored 286 // yet are returned. 287 func CommentTimestampsVerify(tr cmv1.TimestampsReply) ([]uint32, error) { 288 notTimestamped := make([]uint32, 0, len(tr.Comments)) 289 for cid, v := range tr.Comments { 290 err := CommentTimestampVerify(v) 291 if err != nil { 292 if err == backend.ErrNotTimestamped { 293 notTimestamped = append(notTimestamped, cid) 294 continue 295 } 296 return nil, fmt.Errorf("unable to verify comment %v timestamp: %v", 297 cid, err) 298 } 299 } 300 return notTimestamped, nil 301 } 302 303 func convertCommentProof(p cmv1.Proof) backend.Proof { 304 return backend.Proof{ 305 Type: p.Type, 306 Digest: p.Digest, 307 MerkleRoot: p.MerkleRoot, 308 MerklePath: p.MerklePath, 309 ExtraData: p.ExtraData, 310 } 311 } 312 313 func convertCommentTimestamp(t cmv1.Timestamp) backend.Timestamp { 314 proofs := make([]backend.Proof, 0, len(t.Proofs)) 315 for _, v := range t.Proofs { 316 proofs = append(proofs, convertCommentProof(v)) 317 } 318 return backend.Timestamp{ 319 Data: t.Data, 320 Digest: t.Digest, 321 TxID: t.TxID, 322 MerkleRoot: t.MerkleRoot, 323 Proofs: proofs, 324 } 325 }