Skip to content
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
6 changes: 1 addition & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/labstack/gommon v0.4.0
github.com/mozillazg/go-unidecode v0.2.0
github.com/stretchr/testify v1.7.0
github.com/tonkeeper/tongo v1.2.2
golang.org/x/exp v0.0.0-20230116083435-1de6713980de
golang.org/x/text v0.7.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
)
Expand All @@ -16,12 +16,8 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/mattn/go-colorable v0.1.11 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/snksoft/crc v1.1.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064 // indirect
golang.org/x/exp v0.0.0-20230116083435-1de6713980de // indirect
golang.org/x/sys v0.1.0 // indirect
)
8 changes: 0 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,15 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mozillazg/go-unidecode v0.2.0 h1:vFGEzAH9KSwyWmXCOblazEWDh7fOkpmy/Z4ArmamSUc=
github.com/mozillazg/go-unidecode v0.2.0/go.mod h1:zB48+/Z5toiRolOZy9ksLryJ976VIwmDmpQ2quyt1aA=
github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae h1:7smdlrfdcZic4VfsGKD2ulWL804a4GVphr4s7WZxGiY=
github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/snksoft/crc v1.1.0 h1:HkLdI4taFlgGGG1KvsWMpz78PkOC9TkPVpTV/cuWn48=
github.com/snksoft/crc v1.1.0/go.mod h1:5/gUOsgAm7OmIhb6WJzw7w5g2zfJi4FrHYgGPdshE+A=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tonkeeper/tongo v1.2.2 h1:bihFqAFOXGnub59YyoXgdPq1iw9VYwdOXznG9Vz2D4M=
github.com/tonkeeper/tongo v1.2.2/go.mod h1:LdOBjpUz6vLp1EdX3E0XLNks9YI5XMSqaQahfOMrBEY=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064 h1:S25/rfnfsMVgORT4/J61MJ7rdyseOZOyvLIrZEZ7s6s=
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20230116083435-1de6713980de h1:DBWn//IJw30uYCgERoxCg84hWtA97F4wMiKOIh00Uf0=
golang.org/x/exp v0.0.0-20230116083435-1de6713980de/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
99 changes: 89 additions & 10 deletions jetton_verifier.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package scam_backoffice_rules

import (
"encoding/base64"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
"sync"
"time"
"unicode"
Expand All @@ -12,7 +17,6 @@ import (

"github.com/avast/retry-go"
"github.com/labstack/gommon/log"
"github.com/tonkeeper/tongo"
)

const jettonPath = "https://raw.githubusercontent.com/tonkeeper/ton-assets/main/jettons.json"
Expand All @@ -23,13 +27,18 @@ const jettonPath = "https://raw.githubusercontent.com/tonkeeper/ton-assets/main/
type JettonVerifier struct {
// mu protects Jettons
mu sync.RWMutex
jettons map[string]map[tongo.AccountID]jetton
jettons map[string]map[accountID]jetton
}

type jetton struct {
Name string `json:"name"`
Address tongo.AccountID `json:"address"`
Symbol string `json:"symbol"`
Name string `json:"name"`
Address accountID `json:"address"`
Symbol string `json:"symbol"`
}

type accountID struct {
workchain int32
address [32]byte
}

// hardcodedBlacklistedSymbols contains symbols that are known to be used by scam jettons.
Expand Down Expand Up @@ -132,7 +141,7 @@ var manuallyWhitelisted = &unicode.RangeTable{
func NewJettonVerifier() *JettonVerifier {
verifier := JettonVerifier{
// we have valid jettons sharing the same symbol
jettons: map[string]map[tongo.AccountID]jetton{},
jettons: map[string]map[accountID]jetton{},
}
go verifier.run()
return &verifier
Expand All @@ -155,11 +164,11 @@ func (verifier *JettonVerifier) run() {
}

func (verifier *JettonVerifier) updateJettons(knownJettons []jetton) {
jettons := make(map[string]map[tongo.AccountID]jetton, len(knownJettons))
jettons := make(map[string]map[accountID]jetton, len(knownJettons))
for _, item := range knownJettons {
normalized := NormalizeString(item.Symbol)
if _, ok := jettons[normalized]; !ok {
jettons[normalized] = make(map[tongo.AccountID]jetton)
jettons[normalized] = make(map[accountID]jetton)
}
jettons[normalized][item.Address] = item
}
Expand All @@ -169,7 +178,7 @@ func (verifier *JettonVerifier) updateJettons(knownJettons []jetton) {
}

// IsBlacklisted returns true if the jetton SYMBOL is similar to any of the well-known jettons.
func (verifier *JettonVerifier) IsBlacklisted(address tongo.AccountID, symbol string) bool {
func (verifier *JettonVerifier) IsBlacklisted(workchain int32, address [32]byte, symbol string) bool {
for _, s := range symbol {
// if the symbol contains non-printable characters,
// we consider it a scam.
Expand Down Expand Up @@ -197,13 +206,26 @@ func (verifier *JettonVerifier) IsBlacklisted(address tongo.AccountID, symbol st
// no jettons with such symbol
return false
}
if _, ok := jettons[address]; ok {
if _, ok := jettons[accountID{workchain: workchain, address: address}]; ok {
// this jetton is in our list of well-known jettons
return false
}
return true
}

func (id *accountID) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
parsed, err := parseAccountID(s)
if err != nil {
return err
}
*id = parsed
return nil
}

func SetBlacklistedSymbols(blacklistedSymbols []string) {
hardcodedBlacklistedSymbolsMutex.Lock()
hardcodedBlacklistedSymbols = blacklistedSymbols
Expand All @@ -225,3 +247,60 @@ func downloadJettons() ([]jetton, error) {
}
return jettons, nil
}

// parseAccountID accepts the raw form "workchain:hex" and the base64 user-friendly form.
func parseAccountID(s string) (accountID, error) {
s = strings.TrimSpace(s)
if workchain, hash, ok := strings.Cut(s, ":"); ok {
return parseRawAccountID(workchain, hash)
}
return parseFriendlyAccountID(s)
}

func parseRawAccountID(workchain, hash string) (accountID, error) {
var id accountID
wc, err := strconv.ParseInt(workchain, 10, 32)
if err != nil {
return accountID{}, fmt.Errorf("invalid workchain %q: %w", workchain, err)
}
id.workchain = int32(wc)
b, err := hex.DecodeString(hash)
if err != nil || len(b) != len(id.address) {
return accountID{}, fmt.Errorf("invalid account hash %q", hash)
}
copy(id.address[:], b)
return id, nil
}

func parseFriendlyAccountID(s string) (accountID, error) {
var id accountID
b, err := base64.URLEncoding.DecodeString(s)
if err != nil {
b, err = base64.StdEncoding.DecodeString(s)
}
if err != nil || len(b) != 36 {
return accountID{}, fmt.Errorf("invalid account address %q", s)
}
if crc16(b[:34]) != binary.BigEndian.Uint16(b[34:]) {
return accountID{}, fmt.Errorf("invalid account address %q: checksum mismatch", s)
}
id.workchain = int32(int8(b[1]))
copy(id.address[:], b[2:34])
return id, nil
}

// crc16 is CRC-16/XMODEM, the checksum of user-friendly addresses.
func crc16(data []byte) uint16 {
var crc uint16
for _, b := range data {
crc ^= uint16(b) << 8
for i := 0; i < 8; i++ {
if crc&0x8000 != 0 {
crc = crc<<1 ^ 0x1021
} else {
crc <<= 1
}
}
}
return crc
}
Loading
Loading