Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add new helper functions #329

Merged
merged 2 commits into from
Mar 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
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
75 changes: 65 additions & 10 deletions webapi/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/decred/dcrd/dcrutil/v4"
"github.com/decred/dcrd/wire"
"github.com/gin-gonic/gin"
"github.com/decred/vspd/rpc"
)

func currentVoteVersion(params *chaincfg.Params) uint32 {
Expand Down Expand Up @@ -105,16 +105,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) 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: %v", 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")
}

err := dcrutil.VerifyMessage(commitmentAddress, signature, string(reqBytes), cfg.NetParams)
if err != nil {
return err
}
return nil
}
Expand All @@ -141,3 +147,52 @@ 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(hash string) error {
if len(hash) != chainhash.MaxHashStringSize {
return fmt.Errorf("incorrect hash length: got %d, expected %d", len(hash), chainhash.MaxHashStringSize)

}
_, err := chainhash.NewHashFromStr(hash)
if err != nil {
return fmt.Errorf("invalid hash: %v", err)

}

return nil
}

// getCommitmentAddress gets the commitment address of the provided ticket hash
// from the chain.
func getCommitmentAddress(hash string, dcrdClient *rpc.DcrdRPC) (string, error) {
var commitmentAddress string
resp, err := dcrdClient.GetRawTransaction(hash)
if err != nil {
return commitmentAddress, fmt.Errorf("dcrd.GetRawTransaction for ticket failed: %v", err)

}

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

}

err = isValidTicket(msgTx)
if err != nil {
return commitmentAddress, fmt.Errorf("Invalid ticket: %w", errInvalidTicket)

}

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

}

commitmentAddress = addr.String()
return commitmentAddress, nil
}
91 changes: 29 additions & 62 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)
err = validateTicketHash(hash)
if err != nil {
log.Errorf("%s: Invalid hash (clientIP=%s): %v", funcName, c.ClientIP(), err)
log.Errorf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
sendErrorWithMsg("invalid ticket hash", errBadRequest, c)
return
}
Expand All @@ -313,67 +303,44 @@ 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
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
}

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)
commitmentAddress, err = getCommitmentAddress(hash, dcrdClient)
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)
if err != nil {
log.Errorf("%s: AddrFromSStxPkScrCommitment error (ticketHash=%s): %v", funcName, hash, err)
sendError(errInternalError, c)
var apiErr *apiError
if errors.Is(err, apiErr) {
sendError(errInvalidTicket, c)
} else {
sendError(errInternalError, c)
}
log.Errorf("%s: (clientIP: %s, ticketHash: %s): %v", funcName, c.ClientIP(), hash, err)
return
}
}

commitmentAddress = addr.String()
// Ensure a signature is provided.
signature := c.GetHeader("VSP-Client-Signature")
if signature == "" {
log.Warnf("%s: Bad request (clientIP=%s): %v", funcName, c.ClientIP(), err)
sendErrorWithMsg("no VSP-Client-Signature header", errBadRequest, c)
return
}

// Validate request signature to ensure ticket ownership.
err = validateSignature(reqBytes, commitmentAddress, c)
err = validateSignature(hash, commitmentAddress, signature, string(reqBytes))
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: Bad signature (clientIP=%s, ticketHash=%s): %v", funcName, err)
sendError(errBadSignature, c)
return
}

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