github.com/decred/politeia@v1.4.0/politeiawww/cmd/politeiaverify/ticketvote.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 main
     6  
     7  import (
     8  	"encoding/json"
     9  	"fmt"
    10  	"os"
    11  
    12  	backend "github.com/decred/politeia/politeiad/backendv2"
    13  	tkplugin "github.com/decred/politeia/politeiad/plugins/ticketvote"
    14  	tkv1 "github.com/decred/politeia/politeiawww/api/ticketvote/v1"
    15  	"github.com/decred/politeia/politeiawww/client"
    16  )
    17  
    18  // votesBundle represents the bundle that is downloaded from politeiagui for
    19  // DCR ticket votes.
    20  type votesBundle struct {
    21  	Auths           []tkv1.AuthDetails     `json:"auths,omitempty"`
    22  	Details         *tkv1.VoteDetails      `json:"details,omitempty"`
    23  	Votes           []tkv1.CastVoteDetails `json:"votes,omitempty"`
    24  	ServerPublicKey string                 `json:"serverpublickey"`
    25  }
    26  
    27  // verifyVotesBundle takes the filepath of a votes bundle and verifies the
    28  // contents of the file. This includes verifying all signatures of the vote
    29  // authorizations, vote details, and cast votes. The cast votes are also
    30  // checked against the eligible tickets to ensure all cast votes are valid and
    31  // are not duplicates.
    32  func verifyVotesBundle(fp string) error {
    33  	// Decode votes bundle
    34  	b, err := os.ReadFile(fp)
    35  	if err != nil {
    36  		return err
    37  	}
    38  	var vb votesBundle
    39  	err = json.Unmarshal(b, &vb)
    40  	if err != nil {
    41  		return fmt.Errorf("could not unmarshal votes bundle: %v", err)
    42  	}
    43  	if len(vb.Auths) == 0 {
    44  		return fmt.Errorf("vote has not been authorized yet; nothing to verify")
    45  	}
    46  
    47  	fmt.Printf("Token            : %v\n", vb.Auths[0].Token)
    48  	fmt.Printf("Server public key: %v\n", vb.ServerPublicKey)
    49  	fmt.Printf("\n")
    50  
    51  	// Verify vote authorization signatures
    52  	for _, v := range vb.Auths {
    53  		fmt.Printf("Auth action : %v\n", v.Action)
    54  		fmt.Printf("  Public key: %v\n", v.PublicKey)
    55  		fmt.Printf("  Signature : %v\n", v.Signature)
    56  		fmt.Printf("  Receipt   : %v\n", v.Receipt)
    57  		err = client.AuthDetailsVerify(v, vb.ServerPublicKey)
    58  		if err != nil {
    59  			return err
    60  		}
    61  	}
    62  
    63  	fmt.Printf("Authorization signatures and receipts verified!\n")
    64  	fmt.Printf("\n")
    65  
    66  	// Verify the vote details signature
    67  	if vb.Details == nil {
    68  		return fmt.Errorf("vote has not been started; nothing else to verify")
    69  	}
    70  	fmt.Printf("Vote details\n")
    71  	fmt.Printf("  Public key: %v\n", vb.Details.PublicKey)
    72  	fmt.Printf("  Signature : %v\n", vb.Details.Signature)
    73  	fmt.Printf("  Receipt   : %v\n", vb.Details.Receipt)
    74  
    75  	err = client.VoteDetailsVerify(*vb.Details, vb.ServerPublicKey)
    76  	if err != nil {
    77  		return err
    78  	}
    79  
    80  	fmt.Printf("Vote details signature and receipt verified!\n")
    81  	fmt.Printf("\n")
    82  
    83  	// Verify cast votes. This includes verifying the cast vote
    84  	// signature, receipt, verifying that the ticket is eligible to
    85  	// vote, and verifying that the vote is not a duplicate.
    86  	var (
    87  		eligible = make(map[string]struct{}, len(vb.Details.EligibleTickets))
    88  		dups     = make(map[string]struct{}, len(vb.Votes))
    89  
    90  		notEligible = make([]string, 0, 256)
    91  		duplicates  = make([]string, 0, 256)
    92  	)
    93  	for _, v := range vb.Details.EligibleTickets {
    94  		eligible[v] = struct{}{}
    95  	}
    96  
    97  	fmt.Printf("Cast votes: %v/%v\n", len(vb.Votes), len(eligible))
    98  
    99  	fmt.Printf("Checking votes for eligibility, duplicates, and " +
   100  		"valid signatures...\n")
   101  
   102  	for _, v := range vb.Votes {
   103  		err := client.CastVoteDetailsVerify(v, vb.ServerPublicKey)
   104  		if err != nil {
   105  			return fmt.Errorf("could not verify vote %v: %v",
   106  				v.Ticket, err)
   107  		}
   108  		_, ok := eligible[v.Ticket]
   109  		if !ok {
   110  			// This ticket is not eligible to vote
   111  			notEligible = append(notEligible, v.Ticket)
   112  		}
   113  		_, ok = dups[v.Ticket]
   114  		if ok {
   115  			// This vote is a duplicate
   116  			duplicates = append(duplicates, v.Ticket)
   117  		}
   118  		dups[v.Ticket] = struct{}{}
   119  	}
   120  	if len(notEligible) > 0 || len(duplicates) > 0 {
   121  		return fmt.Errorf("cast vote validation failed: not eligible %v, "+
   122  			"duplicates %v", notEligible, duplicates)
   123  	}
   124  
   125  	fmt.Printf("Cast votes verified!\n")
   126  
   127  	return nil
   128  }
   129  
   130  // verifyVoteTimestamps takes the filepath of vote timestamps and verifies the
   131  // validity of all timestamps included in the ticketvote v1 TimestampsReply.
   132  func verifyVoteTimestamps(fp string) error {
   133  	// Decode timestamps reply
   134  	b, err := os.ReadFile(fp)
   135  	if err != nil {
   136  		return err
   137  	}
   138  	var tr tkv1.TimestampsReply
   139  	err = json.Unmarshal(b, &tr)
   140  	if err != nil {
   141  		return err
   142  	}
   143  
   144  	// Verify authorization timestamps
   145  	if len(tr.Auths) == 0 {
   146  		return fmt.Errorf("vote has not been authorized; nothing to verify")
   147  	}
   148  
   149  	fmt.Printf("Vote authorizations: %v\n", len(tr.Auths))
   150  
   151  	for i, v := range tr.Auths {
   152  		err := client.TicketVoteTimestampVerify(v)
   153  		if err != nil {
   154  			return fmt.Errorf("unable to verify authorization timestamp %v: %v",
   155  				i, err)
   156  		}
   157  	}
   158  
   159  	fmt.Printf("Vote authorization timestamps verified!\n")
   160  
   161  	// Verify vote details timestamp
   162  	if tr.Details == nil {
   163  		return fmt.Errorf("vote has not been started; nothing else to verify")
   164  	}
   165  	err = client.TicketVoteTimestampVerify(*tr.Details)
   166  	if err != nil {
   167  		return fmt.Errorf("unable to verify vote details timestamp: %v", err)
   168  	}
   169  
   170  	fmt.Printf("Vote details timestamp verified!\n")
   171  
   172  	// Verify cast vote timestamps
   173  	notTimestamped := make([]string, 0, len(tr.Votes))
   174  	for i, v := range tr.Votes {
   175  		err = client.TicketVoteTimestampVerify(v)
   176  		switch err {
   177  		case nil:
   178  			// Timestamp verified. Check the next one.
   179  			continue
   180  		case backend.ErrNotTimestamped:
   181  			// This ticket has not been timestamped yet. Continue to the
   182  			// code below so that the ticket hash gets printed.
   183  		default:
   184  			// An unexpected error occurred
   185  			return fmt.Errorf("could not verify cast vote timestamp %v: %v",
   186  				i, err)
   187  		}
   188  
   189  		// This vote has not been timestamped yet. Decode the cast vote
   190  		// and save the ticket hash.
   191  		var cvd tkplugin.CastVoteDetails
   192  		err = json.Unmarshal([]byte(v.Data), &cvd)
   193  		if err != nil {
   194  			return fmt.Errorf("could not unmarshal cast vote: %v", err)
   195  		}
   196  		notTimestamped = append(notTimestamped, cvd.Ticket)
   197  	}
   198  
   199  	fmt.Printf("Total votes        : %v\n", len(tr.Votes))
   200  	fmt.Printf("Not timestamped yet: %v\n", len(notTimestamped))
   201  	for _, v := range notTimestamped {
   202  		fmt.Printf("  %v\n", v)
   203  	}
   204  	fmt.Printf("Cast vote timestamps verified!\n")
   205  
   206  	return nil
   207  }