Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 72 additions & 9 deletions webapi/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/decred/dcrd/dcrutil/v4"
"github.com/decred/dcrd/wire"
"github.com/decred/vspd/rpc"
"github.com/gin-gonic/gin"
)

Expand Down Expand Up @@ -105,16 +106,22 @@ func validPolicyOption(policy string) error {
}
}

func validateSignature(reqBytes []byte, commitmentAddress string, c *gin.Context) error {
// Ensure a signature is provided.
signature := c.GetHeader("VSP-Client-Signature")
if signature == "" {
return errors.New("no VSP-Client-Signature header")
}
func validateSignature(hash, commitmentAddress, signature, message string, c *gin.Context) error {
firstErr := dcrutil.VerifyMessage(commitmentAddress, signature, message, cfg.NetParams)
if firstErr != nil {
// Don't return an error straight away if sig validation fails -
// first check if we have an alternate sign address for this ticket.
altSigData, err := db.AltSignAddrData(hash)
if err != nil {
return fmt.Errorf("db.AltSignAddrData failed (ticketHash=%s): %v", hash, err)
}

err := dcrutil.VerifyMessage(commitmentAddress, signature, string(reqBytes), cfg.NetParams)
if err != nil {
return err
// If we have no alternate sign address, or if validating with the
// alt sign addr fails, return an error to the client.
if altSigData == nil || dcrutil.VerifyMessage(altSigData.AltSignAddr, signature, message, cfg.NetParams) != nil {
return fmt.Errorf("Bad signature (clientIP=%s, ticketHash=%s)", c.ClientIP(), hash)
}
return firstErr
}
return nil
}
Expand All @@ -141,3 +148,59 @@ func isValidTicket(tx *wire.MsgTx) error {

return nil
}

// validateTicketHash ensures the provided ticket hash is a valid ticket hash.
// A ticket hash should be 64 chars (MaxHashStringSize) and should parse into
// a chainhash.Hash without error.
func validateTicketHash(c *gin.Context, hash string) (bool, error) {
Comment thread
ukane-philemon marked this conversation as resolved.
Outdated
if len(hash) != chainhash.MaxHashStringSize {
return false, fmt.Errorf("Incorrect hash length (clientIP=%s): got %d, expected %d", c.ClientIP(), len(hash), chainhash.MaxHashStringSize)
Comment thread
ukane-philemon marked this conversation as resolved.
Outdated

}
_, err := chainhash.NewHashFromStr(hash)
if err != nil {
return false, fmt.Errorf("Invalid hash (clientIP=%s): %v", c.ClientIP(), err)
Comment thread
ukane-philemon marked this conversation as resolved.
Outdated

}

return true, nil
}

// getCommitmentAddress gets the commitment address of the provided ticket hash
// from the chain.
func getCommitmentAddress(c *gin.Context, hash string) (string, bool, error) {
var commitmentAddress string
dcrdClient := c.MustGet(dcrdKey).(*rpc.DcrdRPC)
Comment thread
ukane-philemon marked this conversation as resolved.
Outdated
dcrdErr := c.MustGet(dcrdErrorKey)
if dcrdErr != nil {
return commitmentAddress, false, fmt.Errorf("could not get dcrd client: %v", dcrdErr.(error))

}

resp, err := dcrdClient.GetRawTransaction(hash)
if err != nil {
return commitmentAddress, false, fmt.Errorf("dcrd.GetRawTransaction for ticket failed (ticketHash=%s): %v", hash, err)

}

msgTx, err := decodeTransaction(resp.Hex)
if err != nil {
return commitmentAddress, false, fmt.Errorf("Failed to decode ticket hex (ticketHash=%s): %v", hash, err)

}

err = isValidTicket(msgTx)
if err != nil {
return commitmentAddress, true, fmt.Errorf("Invalid ticket (clientIP=%s, ticketHash=%s): %v", c.ClientIP(), hash, err)

}

addr, err := stake.AddrFromSStxPkScrCommitment(msgTx.TxOut[1].PkScript, cfg.NetParams)
if err != nil {
return commitmentAddress, false, fmt.Errorf("AddrFromSStxPkScrCommitment error (ticketHash=%s): %v", hash, err)

}

commitmentAddress = addr.String()
return commitmentAddress, false, nil
}
85 changes: 22 additions & 63 deletions webapi/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import (
"net/http"
"strings"

"github.com/decred/dcrd/blockchain/stake/v4"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/vspd/rpc"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
Expand Down Expand Up @@ -287,17 +285,9 @@ func vspAuth() gin.HandlerFunc {
hash := request.TicketHash

// Before hitting the db or any RPC, ensure this is a valid ticket hash.
// A ticket hash should be 64 chars (MaxHashStringSize) and should parse
// into a chainhash.Hash without error.
if len(hash) != chainhash.MaxHashStringSize {
log.Errorf("%s: Incorrect hash length (clientIP=%s): got %d, expected %d",
funcName, c.ClientIP(), len(hash), chainhash.MaxHashStringSize)
sendErrorWithMsg("invalid ticket hash", errBadRequest, c)
return
}
_, err = chainhash.NewHashFromStr(hash)
if err != nil {
log.Errorf("%s: Invalid hash (clientIP=%s): %v", funcName, c.ClientIP(), err)
validticket, err := validateTicketHash(c, hash)
if !validticket {
log.Errorf("%s: %v", funcName, err)
sendErrorWithMsg("invalid ticket hash", errBadRequest, c)
return
}
Expand All @@ -313,67 +303,36 @@ func vspAuth() gin.HandlerFunc {
// If the ticket was found in the database, we already know its
// commitment address. Otherwise we need to get it from the chain.
var commitmentAddress string
var isInvalid bool
Comment thread
ukane-philemon marked this conversation as resolved.
Outdated

if ticketFound {
commitmentAddress = ticket.CommitmentAddress
} else {
dcrdClient := c.MustGet(dcrdKey).(*rpc.DcrdRPC)
dcrdErr := c.MustGet(dcrdErrorKey)
if dcrdErr != nil {
log.Errorf("%s: could not get dcrd client: %v", funcName, dcrdErr.(error))
sendError(errInternalError, c)
return
}

resp, err := dcrdClient.GetRawTransaction(hash)
if err != nil {
log.Errorf("%s: dcrd.GetRawTransaction for ticket failed (ticketHash=%s): %v", funcName, hash, err)
sendError(errInternalError, c)
return
}

msgTx, err := decodeTransaction(resp.Hex)
if err != nil {
log.Errorf("%s: Failed to decode ticket hex (ticketHash=%s): %v", funcName, ticket.Hash, err)
sendError(errInternalError, c)
return
}

err = isValidTicket(msgTx)
if err != nil {
log.Warnf("%s: Invalid ticket (clientIP=%s, ticketHash=%s): %v", funcName, c.ClientIP(), hash, err)
sendError(errInvalidTicket, c)
return
}

addr, err := stake.AddrFromSStxPkScrCommitment(msgTx.TxOut[1].PkScript, cfg.NetParams)
commitmentAddress, isInvalid, err = getCommitmentAddress(c, hash)
if err != nil {
log.Errorf("%s: AddrFromSStxPkScrCommitment error (ticketHash=%s): %v", funcName, hash, err)
sendError(errInternalError, c)
if isInvalid {
sendError(errInvalidTicket, c)
} else {
sendError(errInternalError, c)
}
log.Errorf("%s: %v", funcName, err)
return
}
}

commitmentAddress = addr.String()
// Ensure a signature is provided.
signature := c.GetHeader("VSP-Client-Signature")
if signature == "" {
sendErrorWithMsg("no VSP-Client-Signature header", errBadRequest, c)
Comment thread
ukane-philemon marked this conversation as resolved.
return
}

// Validate request signature to ensure ticket ownership.
err = validateSignature(reqBytes, commitmentAddress, c)
err = validateSignature(hash, commitmentAddress, signature, string(reqBytes), c)
if err != nil {
// Don't return an error straight away if sig validation fails -
// first check if we have an alternate sign address for this ticket.
altSigData, err := db.AltSignAddrData(hash)
if err != nil {
log.Errorf("%s: db.AltSignAddrData failed (ticketHash=%s): %v", funcName, hash, err)
sendError(errInternalError, c)
return
}

// If we have no alternate sign address, or if validating with the
// alt sign addr fails, return an error to the client.
if altSigData == nil || validateSignature(reqBytes, altSigData.AltSignAddr, c) != nil {
log.Warnf("%s: Bad signature (clientIP=%s, ticketHash=%s)", funcName, c.ClientIP(), hash)
sendError(errBadSignature, c)
return
}
log.Errorf("%s: %v", funcName, err)
sendError(errBadSignature, c)
return
}

// Add ticket information to context so downstream handlers don't need
Expand Down