@@ -6,20 +6,25 @@ package webapi
66
77import (
88 "bytes"
9+ "encoding/base64"
910 "errors"
11+ "fmt"
1012 "io"
1113 "net/http"
1214 "strings"
15+ "time"
1316
14- "github.com/decred/dcrd/blockchain/stake/v4"
15- "github.com/decred/dcrd/chaincfg/chainhash"
1617 "github.com/decred/vspd/rpc"
1718 "github.com/gin-gonic/gin"
1819 "github.com/gin-gonic/gin/binding"
1920 "github.com/gorilla/sessions"
2021 "github.com/jrick/wsrpc/v2"
2122)
2223
24+ // TicketSearchMessageFmt is the format for the message to be signed
25+ // in order to search for a ticket using the vspd frontend.
26+ const TicketSearchMessageFmt = "I want to check vspd ticket status for ticket %s on VSP %s on block window %d."
27+
2328// withSession middleware adds a gorilla session to the request context for
2429// downstream handlers to make use of. Sessions are used by admin pages to
2530// maintain authentication status.
@@ -287,17 +292,9 @@ func vspAuth() gin.HandlerFunc {
287292 hash := request .TicketHash
288293
289294 // Before hitting the db or any RPC, ensure this is a valid ticket hash.
290- // A ticket hash should be 64 chars (MaxHashStringSize) and should parse
291- // into a chainhash.Hash without error.
292- if len (hash ) != chainhash .MaxHashStringSize {
293- log .Errorf ("%s: Incorrect hash length (clientIP=%s): got %d, expected %d" ,
294- funcName , c .ClientIP (), len (hash ), chainhash .MaxHashStringSize )
295- sendErrorWithMsg ("invalid ticket hash" , errBadRequest , c )
296- return
297- }
298- _ , err = chainhash .NewHashFromStr (hash )
299- if err != nil {
300- log .Errorf ("%s: Invalid hash (clientIP=%s): %v" , funcName , c .ClientIP (), err )
295+ validticket , err := validateTicketHash (c , hash )
296+ if ! validticket {
297+ log .Errorf ("%s: %v" , funcName , err )
301298 sendErrorWithMsg ("invalid ticket hash" , errBadRequest , c )
302299 return
303300 }
@@ -313,74 +310,153 @@ func vspAuth() gin.HandlerFunc {
313310 // If the ticket was found in the database, we already know its
314311 // commitment address. Otherwise we need to get it from the chain.
315312 var commitmentAddress string
313+ var isInvalid bool
314+
316315 if ticketFound {
317316 commitmentAddress = ticket .CommitmentAddress
318317 } else {
319- dcrdClient := c .MustGet (dcrdKey ).(* rpc.DcrdRPC )
320- dcrdErr := c .MustGet (dcrdErrorKey )
321- if dcrdErr != nil {
322- log .Errorf ("%s: could not get dcrd client: %v" , funcName , dcrdErr .(error ))
323- sendError (errInternalError , c )
324- return
325- }
326-
327- resp , err := dcrdClient .GetRawTransaction (hash )
318+ commitmentAddress , isInvalid , err = getCommitmentAddress (c , hash )
328319 if err != nil {
329- log .Errorf ("%s: dcrd.GetRawTransaction for ticket failed (ticketHash=%s): %v" , funcName , hash , err )
330- sendError (errInternalError , c )
320+ if isInvalid {
321+ sendError (errInvalidTicket , c )
322+ } else {
323+ sendError (errInternalError , c )
324+ }
325+ log .Errorf ("%s: %v" , funcName , err )
331326 return
332327 }
328+ }
333329
334- msgTx , err := decodeTransaction ( resp . Hex )
335- if err != nil {
336- log . Errorf ( "%s: Failed to decode ticket hex (ticketHash=%s): %v" , funcName , ticket . Hash , err )
337- sendError ( errInternalError , c )
338- return
339- }
330+ // Ensure a signature is provided.
331+ signature := c . GetHeader ( "VSP-Client-Signature" )
332+ if signature == "" {
333+ sendErrorWithMsg ( "no VSP-Client-Signature header" , errBadRequest , c )
334+ return
335+ }
340336
341- err = isValidTicket (msgTx )
342- if err != nil {
343- log .Warnf ("%s: Invalid ticket (clientIP=%s, ticketHash=%s): %v" , funcName , c .ClientIP (), hash , err )
344- sendError (errInvalidTicket , c )
345- return
346- }
337+ // Validate request signature to ensure ticket ownership.
338+ err = validateSignature (hash , commitmentAddress , signature , string (reqBytes ), c )
339+ if err != nil {
340+ log .Errorf ("%s: %v" , funcName , err )
341+ sendError (errBadSignature , c )
342+ return
343+ }
347344
348- addr , err := stake .AddrFromSStxPkScrCommitment (msgTx .TxOut [1 ].PkScript , cfg .NetParams )
349- if err != nil {
350- log .Errorf ("%s: AddrFromSStxPkScrCommitment error (ticketHash=%s): %v" , funcName , hash , err )
351- sendError (errInternalError , c )
352- return
353- }
345+ // Add ticket information to context so downstream handlers don't need
346+ // to access the db for it.
347+ c .Set (ticketKey , ticket )
348+ c .Set (knownTicketKey , ticketFound )
349+ c .Set (commitmentAddressKey , commitmentAddress )
350+ }
351+
352+ }
353+
354+ // ticketSearchAuth middleware reads the request form body and extracts the
355+ // ticket hash and signature from the base64 string provided. The commitment
356+ // address for the ticket is retrieved from the database if it is known, or
357+ // it is retrieved from the chain if not.
358+ // The middleware errors out if required information is not provided or the
359+ // signature does not contain a message signed with the commitment
360+ // address. Ticket information is added to the request context for downstream
361+ // handlers to use.
362+ func ticketSearchAuth () gin.HandlerFunc {
363+ return func (c * gin.Context ) {
364+ funcName := "ticketSearchAuth"
365+
366+ encodedString := c .PostForm ("encoded" )
367+
368+ // Get information added to context.
369+ dcrdClient := c .MustGet (dcrdKey ).(* rpc.DcrdRPC )
370+ dcrdErr := c .MustGet (dcrdErrorKey )
371+ if dcrdErr != nil {
372+ log .Warnf ("%s: %v" , funcName , dcrdErr .(error ))
373+ c .Set (errorKey , errInternalError )
374+ return
375+ }
354376
355- commitmentAddress = addr .String ()
377+ currentBlockHeader , err := dcrdClient .GetBestBlockHeader ()
378+ if err != nil {
379+ log .Errorf ("%s: Error getting best block header : %v" , funcName , err )
380+ c .Set (errorKey , errInternalError )
381+ return
356382 }
357383
358- // Validate request signature to ensure ticket ownership.
359- err = validateSignature (reqBytes , commitmentAddress , c )
384+ // Average blocks per day for the current network.
385+ blocksPerDay := (24 * time .Hour ) / cfg .NetParams .TargetTimePerBlock
386+ blockWindow := int (currentBlockHeader .Height ) / int (blocksPerDay )
387+
388+ decodedByte , err := base64 .StdEncoding .DecodeString (encodedString )
389+ if err != nil {
390+ log .Errorf ("%s: Decoding form data error : %v" , funcName , err )
391+ c .Set (errorKey , errBadRequest )
392+ return
393+ }
394+
395+ data := strings .Split (string (decodedByte ), ":" )
396+ if len (data ) != 2 {
397+ c .Set (errorKey , errBadRequest )
398+ return
399+ }
400+
401+ ticketHash := data [0 ]
402+ signature := data [1 ]
403+ vspURL := cfg .VSPUrl
404+ messageSigned := fmt .Sprintf (TicketSearchMessageFmt , ticketHash , vspURL , blockWindow )
405+
406+ // Before hitting the db or any RPC, ensure this is a valid ticket hash.
407+ validticket , err := validateTicketHash (c , ticketHash )
408+ if ! validticket {
409+ log .Errorf ("%s: %v" , funcName , err )
410+ c .Set (errorKey , errInvalidTicket )
411+ return
412+ }
413+
414+ // Check if this ticket already appears in the database.
415+ ticket , ticketFound , err := db .GetTicketByHash (ticketHash )
360416 if err != nil {
361- // Don't return an error straight away if sig validation fails -
362- // first check if we have an alternate sign address for this ticket.
363- altSigData , err := db .AltSignAddrData (hash )
417+ log .Errorf ("%s: db.GetTicketByHash error (ticketHash=%s): %v" , funcName , ticketHash , err )
418+ c .Set (errorKey , errInternalError )
419+ return
420+ }
421+
422+ if ! ticketFound {
423+ log .Warnf ("%s: Unknown ticket (clientIP=%s)" , funcName , c .ClientIP ())
424+ c .Set (errorKey , errUnknownTicket )
425+ return
426+ }
427+
428+ // If the ticket was found in the database, we already know its
429+ // commitment address. Otherwise we need to get it from the chain.
430+ var commitmentAddress string
431+ var isInvalid bool
432+ if ticketFound {
433+ commitmentAddress = ticket .CommitmentAddress
434+ } else {
435+ commitmentAddress , isInvalid , err = getCommitmentAddress (c , ticketHash )
364436 if err != nil {
365- log .Errorf ("%s: db.AltSignAddrData failed (ticketHash=%s): %v" , funcName , hash , err )
366- sendError (errInternalError , c )
437+ if isInvalid {
438+ c .Set (errorKey , errInvalidTicket )
439+ } else {
440+ c .Set (errorKey , errInternalError )
441+ }
442+ log .Errorf ("%s: %v" , funcName , err )
367443 return
368444 }
445+ }
369446
370- // If we have no alternate sign address, or if validating with the
371- // alt sign addr fails, return an error to the client.
372- if altSigData == nil || validateSignature (reqBytes , altSigData .AltSignAddr , c ) != nil {
373- log .Warnf ("%s: Bad signature (clientIP=%s, ticketHash=%s)" , funcName , c .ClientIP (), hash )
374- sendError (errBadSignature , c )
375- return
376- }
447+ // Validate request signature to ensure ticket ownership.
448+ err = validateSignature (ticketHash , commitmentAddress , signature , messageSigned , c )
449+ if err != nil {
450+ log .Errorf ("%s: %v" , funcName , err )
451+ c .Set (errorKey , errBadSignature )
452+ return
377453 }
378454
379455 // Add ticket information to context so downstream handlers don't need
380456 // to access the db for it.
381457 c .Set (ticketKey , ticket )
382458 c .Set (knownTicketKey , ticketFound )
383- c .Set (commitmentAddressKey , commitmentAddress )
459+ c .Set (errorKey , nil )
384460 }
385461
386462}
0 commit comments