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  }