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 }