Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into multi-device-improv…
Browse files Browse the repository at this point in the history
…ements
  • Loading branch information
pavelbrm committed Jan 26, 2024
2 parents f7d349a + 2d2552a commit f080e45
Show file tree
Hide file tree
Showing 44 changed files with 3,973 additions and 1,869 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,6 @@ share-0.gpg

fetch-quote.json
main/main

go.work
go.work.sum
1 change: 1 addition & 0 deletions docker-compose.dev-refresh.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,4 @@ services:
- UPHOLD_ACCESS_TOKEN
- "RATIOS_SERVICE=https://ratios.rewards.bravesoftware.com"
- RATIOS_TOKEN
- "DAPP_ALLOWED_CORS_ORIGINS=https://my-dapp.com"
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ services:
- TEST_RUN
- TOKEN_LIST
- UPHOLD_ACCESS_TOKEN
- "DAPP_ALLOWED_CORS_ORIGINS=https://my-dapp.com"
volumes:
- ./test/secrets:/etc/kafka/secrets
- ./migrations:/src/migrations
Expand Down Expand Up @@ -167,6 +168,7 @@ services:
- TEST_RUN
- TOKEN_LIST
- UPHOLD_ACCESS_TOKEN
- "DAPP_ALLOWED_CORS_ORIGINS=https://my-dapp.com"
volumes:
- ./test/secrets:/etc/kafka/secrets
- ./migrations:/src/migrations
Expand Down
2 changes: 2 additions & 0 deletions libs/context/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ const (
DisableGeminiLinkingCTXKey CTXKey = "disable_gemini_linking"
// DisableBitflyerLinkingCTXKey - this informs if bitflyer linking is enabled
DisableBitflyerLinkingCTXKey CTXKey = "disable_bitflyer_linking"
// DisableSolanaLinkingCTXKey - this informs if solana linking is enabled
DisableSolanaLinkingCTXKey CTXKey = "disable_solana_linking"

// RadomWebhookSecretCTXKey - the webhook secret key for radom integration
RadomWebhookSecretCTXKey CTXKey = "radom_webhook_secret"
Expand Down
6 changes: 3 additions & 3 deletions libs/custodian/regions.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ func contains(countries, allowblock []string) bool {
for _, ab := range allowblock {
for _, country := range countries {
if strings.EqualFold(ab, country) {
fmt.Println("contains ", ab, country)
return true
}
}
Expand All @@ -141,6 +140,7 @@ type Regions struct {
Gemini GeoAllowBlockMap `json:"gemini" valid:"-"`
Bitflyer GeoAllowBlockMap `json:"bitflyer" valid:"-"`
Zebpay GeoAllowBlockMap `json:"zebpay" valid:"-"`
Solana GeoAllowBlockMap `json:"solana" valid:"-"`
}

// HandleErrors - handle any errors in input
Expand All @@ -149,12 +149,12 @@ func (cr *Regions) HandleErrors(err error) *handlers.AppError {
}

// Decode - implement decodable
func (cr *Regions) Decode(ctx context.Context, input []byte) error {
func (cr *Regions) Decode(_ context.Context, input []byte) error {
return json.Unmarshal(input, cr)
}

// Validate - implement validatable
func (cr *Regions) Validate(ctx context.Context) error {
func (cr *Regions) Validate(_ context.Context) error {
isValid, err := govalidator.ValidateStruct(cr)
if err != nil {
return err
Expand Down
51 changes: 50 additions & 1 deletion libs/custodian/regions_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
package custodian

import "testing"
import (
"context"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestVerdictAllowList(t *testing.T) {
gabm := GeoAllowBlockMap{
Expand All @@ -27,3 +33,46 @@ func TestVerdictBlockList(t *testing.T) {
t.Error("should have been false, US in block list")
}
}

func TestRegions_Decode(t *testing.T) {
type tcGiven struct {
input []byte
}

type exp struct {
allow []string
block []string
}

type testCase struct {
name string
given tcGiven
exp exp
}

testCases := []testCase{
{
name: "solana",
given: tcGiven{
input: []byte(`{"solana":{"allow":["AA"],"block":["AB"]}}`),
},
exp: exp{
allow: []string{"AA"},
block: []string{"AB"},
},
},
}

for i := range testCases {
tc := testCases[i]

t.Run(tc.name, func(t *testing.T) {
regions := Regions{}
err := regions.Decode(context.Background(), tc.given.input)
require.NoError(t, err)

assert.Equal(t, tc.exp.allow, regions.Solana.Allow)
assert.Equal(t, tc.exp.block, regions.Solana.Block)
})
}
}
6 changes: 3 additions & 3 deletions libs/datastore/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import (
appctx "github.com/brave-intl/bat-go/libs/context"
"github.com/brave-intl/bat-go/libs/logging"
"github.com/brave-intl/bat-go/libs/metrics"
sentry "github.com/getsentry/sentry-go"
migrate "github.com/golang-migrate/migrate/v4"
"github.com/getsentry/sentry-go"
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database/postgres"
"github.com/jmoiron/sqlx"
"github.com/prometheus/client_golang/prometheus"
Expand All @@ -41,7 +41,7 @@ var (
}
dbs = map[string]*sqlx.DB{}
// CurrentMigrationVersion holds the default migration version
CurrentMigrationVersion = uint(63)
CurrentMigrationVersion = uint(66)
// MigrationTracks holds the migration version for a given track (eyeshade, promotion, wallet)
MigrationTracks = map[string]uint{
"eyeshade": 20,
Expand Down
7 changes: 0 additions & 7 deletions libs/ptr/ptr.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,8 @@ package ptr

import (
"time"

uuid "github.com/satori/go.uuid"
)

// FromUUID returns pointer to uuid
func FromUUID(u uuid.UUID) *uuid.UUID {
return &u
}

// FromString returns pointer to string
func FromString(s string) *string {
return &s
Expand Down
204 changes: 204 additions & 0 deletions libs/wallet/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@ package wallet

import (
"context"
"crypto/ed25519"
"encoding/base64"
"encoding/hex"
"fmt"
"strings"
"time"

"github.com/brave-intl/bat-go/libs/altcurrency"
"github.com/btcsuite/btcutil/base58"
uuid "github.com/satori/go.uuid"
"github.com/shopspring/decimal"
)
Expand Down Expand Up @@ -89,3 +94,202 @@ type Wallet interface {
// ListTransactions for this wallet, limit number of transactions returned
ListTransactions(ctx context.Context, limit int, startDate time.Time) ([]TransactionInfo, error)
}

type SolanaLinkReq struct {
Pub string
Sig string
Msg string
Nonce string
}

func (w *Info) LinkSolanaAddress(_ context.Context, s SolanaLinkReq) error {
if err := w.isLinkReqValid(s); err != nil {
return newLinkSolanaAddressError(err)
}

w.UserDepositDestination = s.Pub

return nil
}

func (w *Info) isLinkReqValid(s SolanaLinkReq) error {
if err := verifySolanaSignature(s.Pub, s.Msg, s.Sig); err != nil {
return err
}

p := newSolMsgParser(w.ID, s.Pub, s.Nonce)
rm, err := p.parse(s.Msg)
if err != nil {
return fmt.Errorf("parsing error: %w", err)
}

if err := verifyRewardsSignature(w.PublicKey, rm.msg, rm.sig); err != nil {
return err
}

return nil
}

const publicKeyLength = 32

const (
errBadPublicKeyLength Error = "wallet: bad public key length"
errInvalidSolanaSignature Error = "wallet: invalid solana signature for message and public key"
)

func verifySolanaSignature(pub, msg, sig string) error {
b := base58.Decode(pub)
if len(b) != publicKeyLength {
return fmt.Errorf("error verifying solana signature: %w", errBadPublicKeyLength)
}
pubKey := ed25519.PublicKey(b)

decSig, err := base64.URLEncoding.DecodeString(sig)
if err != nil {
return fmt.Errorf("error decoding solana signature: %w", err)
}

if !ed25519.Verify(pubKey, []byte(msg), decSig) {
return errInvalidSolanaSignature
}

return nil
}

type rewMsg struct {
msg string
sig string
}

const (
errInvalidPartsLineBreak Error = "wallet: invalid number of lines"
errInvalidPartsColon Error = "wallet: invalid parts colon"
errInvalidParts Error = "wallet: invalid number of parts"
errInvalidPaymentID Error = "wallet: payment id does not match"
errInvalidSolanaPubKey Error = "wallet: solana public key does not match"
errInvalidRewardsMessage Error = "wallet: invalid rewards message"
)

type solMsgParser struct {
paymentID string
solPub string
nonce string
}

func newSolMsgParser(paymentID, solPub, nonce string) solMsgParser {
return solMsgParser{
paymentID: paymentID,
solPub: solPub,
nonce: nonce,
}
}

// parse parses a linking message and returns the rewards part.
//
// For a message to parse successfully it must be a valid format and successfully match the
// configured parser parameters paymentID, solPub and nonce.
//
// The message format should be three lines with a colon delimiter. For example,
// <some-text>:<rewards-payment-id>
// <some-text>:<solana-address>
// <some-text>:<rewards-payment-id>.<nonce>.<rewardsSignature>
//
// The final part is referred to as the rewards message and has the
// format <rewards-payment-id>.<nonce>.<rewardsSignature> we only need to compare
// the <rewards-payment-id>.<nonce>. when parsing.
//
// The returned rewMsg will contain both the message and signature. For example,
//
// rewMsg {
// msg: <rewards-payment-id>.<nonce>,
// sig: <rewardsSignature>,
// }
func (s *solMsgParser) parse(msg string) (rewMsg, error) {
lines := strings.Split(msg, "\n")
if len(lines) != 3 {
return rewMsg{}, errInvalidPartsLineBreak
}

parts := make([]string, 0, 3)
for i := range lines {
p := strings.Split(lines[i], ":")
if len(p) != 2 {
return rewMsg{}, errInvalidPartsColon
}
parts = append(parts, p[1])
}

if len(parts) != 3 {
return rewMsg{}, errInvalidParts
}

if parts[0] != s.paymentID {
return rewMsg{}, errInvalidPaymentID
}

if parts[1] != s.solPub {
return rewMsg{}, errInvalidSolanaPubKey
}

// Compare the final part minus the signature.
exp := s.paymentID + "." + s.nonce + "."
for i := range exp {
if parts[2][i] != exp[i] {
return rewMsg{}, errInvalidRewardsMessage
}
}

rm := rewMsg{
msg: parts[2][:len(exp)-1], // -1 removes the trailing .
sig: parts[2][len(exp):],
}

return rm, nil
}

const errInvalidRewardsSignature Error = "wallet: invalid rewards signature for message and public key"

func verifyRewardsSignature(pub, msg, sig string) error {
b, err := hex.DecodeString(pub)
if err != nil {
return fmt.Errorf("error decoding rewards public key: %w", err)
}

if len(b) != publicKeyLength {
return fmt.Errorf("error verifying rewards signature: %w", errBadPublicKeyLength)
}
pubKey := ed25519.PublicKey(b)

decSig, err := base64.URLEncoding.DecodeString(sig)
if err != nil {
return fmt.Errorf("error decoding rewards signature: %w", err)
}

if !ed25519.Verify(pubKey, []byte(msg), decSig) {
return errInvalidRewardsSignature
}

return nil
}

type LinkSolanaAddressError struct {
err error
}

func newLinkSolanaAddressError(err error) error {
return &LinkSolanaAddressError{err: err}
}

func (e *LinkSolanaAddressError) Error() string {
return e.err.Error()
}

func (e *LinkSolanaAddressError) Unwrap() error {
return e.err
}

type Error string

func (e Error) Error() string {
return string(e)
}
Loading

0 comments on commit f080e45

Please sign in to comment.