diff --git a/api/handler/checksum_test.go b/api/handler/checksum_test.go new file mode 100644 index 00000000..1c708c3a --- /dev/null +++ b/api/handler/checksum_test.go @@ -0,0 +1,17 @@ +package handler + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSanitizeChecksumArray(t *testing.T) { + arrayByValue := []string{"0x44a4b9E2A69d86BA382a511f845CbF2E31286770"} + var cxParams []*string + for _, p := range arrayByValue { + cxParams = append(cxParams, &p) + } + SanitizeChecksums(cxParams...) + assert.Equal(t, *cxParams[0], "0x44A4b9E2A69d86BA382a511f845CbF2E31286770") +} diff --git a/api/handler/common.go b/api/handler/common.go index 93f11417..d1f710c3 100644 --- a/api/handler/common.go +++ b/api/handler/common.go @@ -4,10 +4,12 @@ import ( "fmt" "net/http" "os" + "regexp" "strings" "time" service "github.com/String-xyz/string-api/pkg/service" + "golang.org/x/crypto/sha3" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "github.com/labstack/echo/v4" @@ -116,3 +118,32 @@ func DeleteAuthCookies(c echo.Context) error { func IsLocalEnv() bool { return os.Getenv("ENV") == "local" } + +func validAddress(addr string) bool { + re := regexp.MustCompile("^0x[0-9a-fA-F]{40}$") + return re.MatchString(addr) +} + +func SanitizeChecksums(addrs ...*string) { + for _, addr := range addrs { + if !validAddress(*addr) { + continue + } + lowerCase := strings.ToLower(*addr)[2:] + hash := sha3.NewLegacyKeccak256() + hash.Write([]byte(lowerCase)) + hashBytes := hash.Sum(nil) + + valid := "0x" + for i, b := range lowerCase { + c := string(b) + if b < '0' || b > '9' { + if hashBytes[i/2]&byte(128-i%2*120) != 0 { + c = string(b - 32) + } + } + valid += c + } + *addr = valid + } +} diff --git a/api/handler/login.go b/api/handler/login.go index 63524802..be08c07a 100644 --- a/api/handler/login.go +++ b/api/handler/login.go @@ -34,7 +34,7 @@ func (l login) NoncePayload(c echo.Context) error { if walletAddress == "" { return BadRequestError(c, "WalletAddress must be provided") } - + SanitizeChecksums(&walletAddress) payload, err := l.Service.PayloadToSign(walletAddress) if err != nil { LogStringError(c, err, "login: request wallet login") @@ -85,6 +85,8 @@ func (l login) RefreshToken(c echo.Context) error { return InvalidPayloadError(c, err) } + SanitizeChecksums(&body.WalletAddress) + cookie, err := c.Cookie("refresh_token") if err != nil { LogStringError(c, err, "RefreshToken: unable to get refresh_token cookie") diff --git a/api/handler/quotes.go b/api/handler/quotes.go index 8c5a180a..e736a28b 100644 --- a/api/handler/quotes.go +++ b/api/handler/quotes.go @@ -30,6 +30,12 @@ func (q quote) Quote(c echo.Context) error { LogStringError(c, err, "quote: quote bind") return BadRequestError(c) } + SanitizeChecksums(&body.CxAddr, &body.UserAddress) + // Sanitize Checksum for body.CxParams? It might look like this: + for i := range body.CxParams { + SanitizeChecksums(&body.CxParams[i]) + } + // userId := c.Get("userId").(string) res, err := q.Service.Quote(body) // TODO: pass in userId and use it if err != nil && errors.Cause(err).Error() == "w3: response handling failed: execution reverted" { diff --git a/api/handler/transact.go b/api/handler/transact.go index 071df638..0178fb3d 100644 --- a/api/handler/transact.go +++ b/api/handler/transact.go @@ -29,6 +29,11 @@ func (t transaction) Transact(c echo.Context) error { LogStringError(c, err, "transact: execute bind") return BadRequestError(c) } + SanitizeChecksums(&body.CxAddr, &body.UserAddress) + // Sanitize Checksum for body.CxParams? It might look like this: + for i := range body.CxParams { + SanitizeChecksums(&body.CxParams[i]) + } userId := c.Get("userId").(string) deviceId := c.Get("deviceId").(string) res, err := t.Service.Execute(body, userId, deviceId) diff --git a/pkg/internal/common/evm.go b/pkg/internal/common/evm.go index 15835ddb..c62acc0f 100644 --- a/pkg/internal/common/evm.go +++ b/pkg/internal/common/evm.go @@ -90,9 +90,7 @@ func IsWallet(addr string) bool { if !validAddress(addr) { return false } - if !validChecksum(addr) { - return false - } + addr = SanitizeChecksum(addr) // Copy correct checksum, although endpoint handlers are doing this already address := common.HexToAddress(addr) bytecode, err := geth.CodeAt(context.Background(), address, nil) @@ -104,6 +102,11 @@ func IsWallet(addr string) bool { } func validChecksum(addr string) bool { + valid := SanitizeChecksum(addr) + return addr == valid +} + +func SanitizeChecksum(addr string) string { lowerCase := strings.ToLower(addr)[2:] hash := sha3.NewLegacyKeccak256() hash.Write([]byte(lowerCase)) @@ -119,7 +122,7 @@ func validChecksum(addr string) bool { } valid += c } - return addr == valid + return valid } func validAddress(addr string) bool { diff --git a/pkg/internal/common/evm_test.go b/pkg/internal/common/evm_test.go new file mode 100644 index 00000000..c541948a --- /dev/null +++ b/pkg/internal/common/evm_test.go @@ -0,0 +1,22 @@ +package common + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestChecksumValid(t *testing.T) { + valid := validChecksum("0x44A4b9E2A69d86BA382a511f845CbF2E31286770") + assert.Equal(t, valid, true) +} + +func TestChecksumInvalid(t *testing.T) { + valid := validChecksum("0x44a4b9E2A69d86BA382a511f845CbF2E31286770") + assert.Equal(t, valid, false) +} + +func TestSanitizeChecksum(t *testing.T) { + valid := SanitizeChecksum("0x44a4b9E2A69d86BA382a511f845CbF2E31286770") + assert.Equal(t, valid, "0x44A4b9E2A69d86BA382a511f845CbF2E31286770") +} diff --git a/pkg/service/auth.go b/pkg/service/auth.go index 668b9b0c..48f256e3 100644 --- a/pkg/service/auth.go +++ b/pkg/service/auth.go @@ -22,6 +22,8 @@ type SignablePayload struct { var hexRegex *regexp.Regexp = regexp.MustCompile(`^0x[a-fA-F0-9]{40}$`) +// var walletAuthenticationPrefix string = "" // For testing locally + var walletAuthenticationPrefix string = "Thank you for using String! By signing this message you are:\n\n1) Authorizing String to initiate off-chain transactions on your behalf, including your bank account, credit card, or debit card.\n\n2) Confirming that this wallet is owned by you.\n\nThis request will not trigger any blockchain transaction or cost any gas.\n\nNonce: " type RefreshTokenResponse struct { diff --git a/pkg/service/checkout.go b/pkg/service/checkout.go index d98195b2..fd7db0c6 100644 --- a/pkg/service/checkout.go +++ b/pkg/service/checkout.go @@ -56,22 +56,27 @@ func AuthorizeCharge(amount float64, userWallet string, tokenId string) (auth Au } client := payments.NewClient(*config) - // Generate a payment token ID in case we don't yet have one in the front end - // For testing purposes only - card := tokens.Card{ - Type: checkoutCommon.Card, - Number: "4242424242424242", - ExpiryMonth: 2, - ExpiryYear: 2024, - Name: "Customer Name", - CVV: "100", - } - paymentToken, err := CreateToken(&card) - if err != nil { - return auth, common.StringError(err) - } - paymentTokenID := paymentToken.Created.Token - if tokenId != "" { + var paymentTokenID string + if common.IsLocalEnv() { + // Generate a payment token ID in case we don't yet have one in the front end + // For testing purposes only + card := tokens.Card{ + Type: checkoutCommon.Card, + Number: "4242424242424242", + ExpiryMonth: 2, + ExpiryYear: 2024, + Name: "Customer Name", + CVV: "100", + } + paymentToken, err := CreateToken(&card) + if err != nil { + return auth, common.StringError(err) + } + paymentTokenID = paymentToken.Created.Token + if tokenId != "" { + paymentTokenID = tokenId + } + } else { paymentTokenID = tokenId }