Skip to content

Commit

Permalink
Relayers OFAC (#1313)
Browse files Browse the repository at this point in the history
* ofac

* ofac

* adds log

* imports

* space

* decode

* fix test

* refactor

* tests work

* ofac

* ethereum side

* decode message

* parachain side

* finish off relayers ofac

* revert testing change

* fixes

* return err

* return early

* add log
  • Loading branch information
claravanstaden authored Oct 19, 2024
1 parent 6d11768 commit e87ddb2
Show file tree
Hide file tree
Showing 15 changed files with 608 additions and 10 deletions.
33 changes: 33 additions & 0 deletions relayer/chain/parachain/address.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package parachain

import (
"encoding/hex"
"fmt"
"strings"

"github.com/decred/base58"
"golang.org/x/crypto/blake2b"
)

func SS58Encode(pubKeyHex string, ss58Prefix uint8) (string, error) {
if strings.HasPrefix(pubKeyHex, "0x") {
pubKeyHex = pubKeyHex[2:]
}

pubKey, err := hex.DecodeString(pubKeyHex)
if err != nil {
return "", fmt.Errorf("failed to decode hex: %w", err)
}

address := append([]byte{ss58Prefix}, pubKey...)

hashInput := append([]byte("SS58PRE"), address...)

hash := blake2b.Sum512(hashInput)
checksum := hash[:2]

fullAddress := append(address, checksum...)

ss58Addr := base58.Encode(fullAddress)
return ss58Addr, nil
}
14 changes: 14 additions & 0 deletions relayer/chain/parachain/address_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package parachain

import (
assert "github.com/stretchr/testify/require"
"testing"
)

func TestSS58Prefix(t *testing.T) {
address := "0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"

ss58Address, err := SS58Encode(address, 1)
assert.NoError(t, err)
assert.Equal(t, "A1k3praCLftTgBTb6aVavh3UNKwXN599Fqov17MkEy6bwCU", ss58Address)
}
112 changes: 112 additions & 0 deletions relayer/chain/parachain/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package parachain

import (
"errors"
"fmt"
"strings"

Expand Down Expand Up @@ -125,3 +126,114 @@ func removeLeadingZeroHashForSlice(s []string) []string {
func removeLeadingZeroHash(s string) string {
return strings.Replace(s, "0x", "", 1)
}

type Destination struct {
Variant types.U8
DestinationBytes types.Data
}

type ForeignAccountId32 struct {
ParaID uint32
ID types.H256
Fee types.U128
}

type ForeignAccountId20 struct {
ParaID uint32
ID types.H160
Fee types.U128
}

type RegisterToken struct {
Token types.H160
Fee types.U128
}

type SendToken struct {
Token types.H160
Destination Destination
}

type SendNativeToken struct {
TokenID types.H256
Destination Destination
}

type InboundMessage struct {
Version types.U8
ChainID types.U64
Command types.U8
CommandBytes types.Data
}

func GetDestination(input []byte) (string, error) {
var inboundMessage = &InboundMessage{}
err := types.DecodeFromBytes(input, inboundMessage)
if err != nil {
return "", fmt.Errorf("failed to decode message: %v", err)
}

address := ""
switch inboundMessage.Command {
case 0:
// Register token does not have a destination
break
case 1:
// Send token has a destination
var command = &SendToken{}
err = types.DecodeFromBytes(inboundMessage.CommandBytes, command)
if err != nil {
return "", fmt.Errorf("failed to decode send token command: %v", err)
}

address, err = decodeDestination(command.Destination.Variant, command.Destination.DestinationBytes)
if err != nil {
return "", fmt.Errorf("decode destination: %v", err)
}
case 2:
// Send native token has a destination
var command = &SendNativeToken{}
err = types.DecodeFromBytes(inboundMessage.CommandBytes, command)
if err != nil {
return "", fmt.Errorf("failed to decode send native token command: %v", err)
}

address, err = decodeDestination(command.Destination.Variant, command.Destination.DestinationBytes)
if err != nil {
return "", fmt.Errorf("decode destination: %v", err)
}
}

return address, nil
}

func decodeDestination(variant types.U8, destinationBytes []byte) (string, error) {
switch variant {
case 0:
// Account32
account32 := &types.H256{}
err := types.DecodeFromBytes(destinationBytes, account32)
if err != nil {
return "", fmt.Errorf("failed to decode destination: %v", err)
}
return account32.Hex(), nil
case 1:
// Account32 on destination parachain
var account = &ForeignAccountId32{}
err := types.DecodeFromBytes(destinationBytes, account)
if err != nil {
return "", fmt.Errorf("failed to decode foreign account: %v", err)
}
return account.ID.Hex(), nil
case 2:
// Account20
var account = &ForeignAccountId20{}
err := types.DecodeFromBytes(destinationBytes, account)
if err != nil {
return "", fmt.Errorf("failed to decode foreign account: %v", err)
}
return account.ID.Hex(), nil
}

return "", errors.New("destination variant could not be matched")
}
33 changes: 33 additions & 0 deletions relayer/chain/parachain/message_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package parachain

import (
"testing"

gethCommon "github.com/ethereum/go-ethereum/common"
assert "github.com/stretchr/testify/require"
)

func TestGetDestination(t *testing.T) {
registerTokenPayload := "00a736aa000000000000774667629726ec1fabebcec0d9139bd1c8f72a2300e87648170000000000000000000000"
decodePayloadAndCompareDestinationAddress(t, registerTokenPayload, "") // register token does not have a destination

sendTokenPayload := "00a736aa000000000001774667629726ec1fabebcec0d9139bd1c8f72a23008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4800c16ff2862300000000000000000000e87648170000000000000000000000"
bobAddress := "0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"
decodePayloadAndCompareDestinationAddress(t, sendTokenPayload, bobAddress)

sendTokenToPayload := "00a736aa000000000001774667629726ec1fabebcec0d9139bd1c8f72a2301d00700001cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c00286bee000000000000000000000000000064a7b3b6e00d000000000000000000e87648170000000000000000000000"
ferdieAddress := "0x1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c"
decodePayloadAndCompareDestinationAddress(t, sendTokenToPayload, ferdieAddress)

sendNativeTokenPayload := "00a736aa0000000000022121cfe35065c0c33465fbada265f08e9613428a4b9eb4bb717cd7db2abf622e008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48065cd1d00000000000000000000000000e87648170000000000000000000000"
decodePayloadAndCompareDestinationAddress(t, sendNativeTokenPayload, bobAddress)
}

func decodePayloadAndCompareDestinationAddress(t *testing.T, payload, expectedAddress string) {
data := gethCommon.Hex2Bytes(payload)

destination, err := GetDestination(data)
assert.NoError(t, err)

assert.Equal(t, expectedAddress, destination)
}
12 changes: 12 additions & 0 deletions relayer/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ type EthereumConfig struct {
GasLimit uint64 `mapstructure:"gas-limit"`
}

type OFACConfig struct {
Enabled bool `mapstructure:"enabled"`
ApiKey string `mapstructure:"apiKey"`
}

func (p ParachainConfig) Validate() error {
if p.Endpoint == "" {
return errors.New("[endpoint] is not set")
Expand All @@ -41,3 +46,10 @@ func (p PolkadotConfig) Validate() error {
}
return nil
}

func (o OFACConfig) Validate() error {
if o.Enabled && o.ApiKey == "" {
return errors.New("OFAC is enabled but no [apiKey] set")
}
return nil
}
89 changes: 89 additions & 0 deletions relayer/ofac/ofac.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package ofac

import (
"encoding/json"
"fmt"
"io"
"net/http"

log "github.com/sirupsen/logrus"
)

type OFAC struct {
enabled bool
apiKey string
}

type Response struct {
Identifications []struct {
Category string `json:"category"`
Name string `json:"name"`
Description string `json:"description"`
URL string `json:"url"`
} `json:"identifications"`
}

func New(enabled bool, apiKey string) *OFAC {
return &OFAC{enabled, apiKey}
}

func (o OFAC) IsBanned(source, destination string) (bool, error) {
if !o.enabled {
return false, nil
}

if source != "" {
isSourcedBanned, err := o.isOFACListed(source)
if err != nil {
return true, err
}
if isSourcedBanned {
log.WithField("source", source).Warn("found ofac banned source address")
return true, nil
}
}

if destination != "" {
isDestinationBanned, err := o.isOFACListed(destination)
if err != nil {
return true, err
}
if isDestinationBanned {
log.WithField("destination", destination).Warn("found ofac banned destination address")
return true, nil
}
}

return false, nil
}

func (o OFAC) isOFACListed(address string) (bool, error) {
client := &http.Client{}

req, err := http.NewRequest("GET", fmt.Sprintf("https://public.chainalysis.com/api/v1/address/%s", address), nil)
if err != nil {
return true, err
}

req.Header.Add("Accept", "application/json")
req.Header.Add("X-API-Key", o.apiKey)

resp, err := client.Do(req)
if err != nil {
return true, err
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return true, err
}

var response Response
err = json.Unmarshal(body, &response)
if err != nil {
return true, err
}

return len(response.Identifications) > 0, nil
}
16 changes: 11 additions & 5 deletions relayer/relays/execution/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import (
)

type Config struct {
Source SourceConfig `mapstructure:"source"`
Sink SinkConfig `mapstructure:"sink"`
InstantVerification bool `mapstructure:"instantVerification"`
Schedule ScheduleConfig `mapstructure:"schedule"`
Source SourceConfig `mapstructure:"source"`
Sink SinkConfig `mapstructure:"sink"`
InstantVerification bool `mapstructure:"instantVerification"`
Schedule ScheduleConfig `mapstructure:"schedule"`
OFAC config.OFACConfig `mapstructure:"ofac"`
}

type ScheduleConfig struct {
Expand Down Expand Up @@ -46,7 +47,8 @@ type ContractsConfig struct {
}

type SinkConfig struct {
Parachain beaconconf.ParachainConfig `mapstructure:"parachain"`
Parachain beaconconf.ParachainConfig `mapstructure:"parachain"`
SS58Prefix uint8 `mapstructure:"ss58Prefix"`
}

type ChannelID [32]byte
Expand All @@ -70,5 +72,9 @@ func (c Config) Validate() error {
if err != nil {
return fmt.Errorf("schedule config: %w", err)
}
err = c.OFAC.Validate()
if err != nil {
return fmt.Errorf("ofac config: %w", err)
}
return nil
}
Loading

0 comments on commit e87ddb2

Please sign in to comment.