Skip to content

Commit e87ddb2

Browse files
Relayers OFAC (#1313)
* 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
1 parent 6d11768 commit e87ddb2

File tree

15 files changed

+608
-10
lines changed

15 files changed

+608
-10
lines changed

relayer/chain/parachain/address.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package parachain
2+
3+
import (
4+
"encoding/hex"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/decred/base58"
9+
"golang.org/x/crypto/blake2b"
10+
)
11+
12+
func SS58Encode(pubKeyHex string, ss58Prefix uint8) (string, error) {
13+
if strings.HasPrefix(pubKeyHex, "0x") {
14+
pubKeyHex = pubKeyHex[2:]
15+
}
16+
17+
pubKey, err := hex.DecodeString(pubKeyHex)
18+
if err != nil {
19+
return "", fmt.Errorf("failed to decode hex: %w", err)
20+
}
21+
22+
address := append([]byte{ss58Prefix}, pubKey...)
23+
24+
hashInput := append([]byte("SS58PRE"), address...)
25+
26+
hash := blake2b.Sum512(hashInput)
27+
checksum := hash[:2]
28+
29+
fullAddress := append(address, checksum...)
30+
31+
ss58Addr := base58.Encode(fullAddress)
32+
return ss58Addr, nil
33+
}
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package parachain
2+
3+
import (
4+
assert "github.com/stretchr/testify/require"
5+
"testing"
6+
)
7+
8+
func TestSS58Prefix(t *testing.T) {
9+
address := "0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"
10+
11+
ss58Address, err := SS58Encode(address, 1)
12+
assert.NoError(t, err)
13+
assert.Equal(t, "A1k3praCLftTgBTb6aVavh3UNKwXN599Fqov17MkEy6bwCU", ss58Address)
14+
}

relayer/chain/parachain/message.go

+112
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package parachain
55

66
import (
7+
"errors"
78
"fmt"
89
"strings"
910

@@ -125,3 +126,114 @@ func removeLeadingZeroHashForSlice(s []string) []string {
125126
func removeLeadingZeroHash(s string) string {
126127
return strings.Replace(s, "0x", "", 1)
127128
}
129+
130+
type Destination struct {
131+
Variant types.U8
132+
DestinationBytes types.Data
133+
}
134+
135+
type ForeignAccountId32 struct {
136+
ParaID uint32
137+
ID types.H256
138+
Fee types.U128
139+
}
140+
141+
type ForeignAccountId20 struct {
142+
ParaID uint32
143+
ID types.H160
144+
Fee types.U128
145+
}
146+
147+
type RegisterToken struct {
148+
Token types.H160
149+
Fee types.U128
150+
}
151+
152+
type SendToken struct {
153+
Token types.H160
154+
Destination Destination
155+
}
156+
157+
type SendNativeToken struct {
158+
TokenID types.H256
159+
Destination Destination
160+
}
161+
162+
type InboundMessage struct {
163+
Version types.U8
164+
ChainID types.U64
165+
Command types.U8
166+
CommandBytes types.Data
167+
}
168+
169+
func GetDestination(input []byte) (string, error) {
170+
var inboundMessage = &InboundMessage{}
171+
err := types.DecodeFromBytes(input, inboundMessage)
172+
if err != nil {
173+
return "", fmt.Errorf("failed to decode message: %v", err)
174+
}
175+
176+
address := ""
177+
switch inboundMessage.Command {
178+
case 0:
179+
// Register token does not have a destination
180+
break
181+
case 1:
182+
// Send token has a destination
183+
var command = &SendToken{}
184+
err = types.DecodeFromBytes(inboundMessage.CommandBytes, command)
185+
if err != nil {
186+
return "", fmt.Errorf("failed to decode send token command: %v", err)
187+
}
188+
189+
address, err = decodeDestination(command.Destination.Variant, command.Destination.DestinationBytes)
190+
if err != nil {
191+
return "", fmt.Errorf("decode destination: %v", err)
192+
}
193+
case 2:
194+
// Send native token has a destination
195+
var command = &SendNativeToken{}
196+
err = types.DecodeFromBytes(inboundMessage.CommandBytes, command)
197+
if err != nil {
198+
return "", fmt.Errorf("failed to decode send native token command: %v", err)
199+
}
200+
201+
address, err = decodeDestination(command.Destination.Variant, command.Destination.DestinationBytes)
202+
if err != nil {
203+
return "", fmt.Errorf("decode destination: %v", err)
204+
}
205+
}
206+
207+
return address, nil
208+
}
209+
210+
func decodeDestination(variant types.U8, destinationBytes []byte) (string, error) {
211+
switch variant {
212+
case 0:
213+
// Account32
214+
account32 := &types.H256{}
215+
err := types.DecodeFromBytes(destinationBytes, account32)
216+
if err != nil {
217+
return "", fmt.Errorf("failed to decode destination: %v", err)
218+
}
219+
return account32.Hex(), nil
220+
case 1:
221+
// Account32 on destination parachain
222+
var account = &ForeignAccountId32{}
223+
err := types.DecodeFromBytes(destinationBytes, account)
224+
if err != nil {
225+
return "", fmt.Errorf("failed to decode foreign account: %v", err)
226+
}
227+
return account.ID.Hex(), nil
228+
case 2:
229+
// Account20
230+
var account = &ForeignAccountId20{}
231+
err := types.DecodeFromBytes(destinationBytes, account)
232+
if err != nil {
233+
return "", fmt.Errorf("failed to decode foreign account: %v", err)
234+
}
235+
return account.ID.Hex(), nil
236+
}
237+
238+
return "", errors.New("destination variant could not be matched")
239+
}
+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package parachain
2+
3+
import (
4+
"testing"
5+
6+
gethCommon "github.com/ethereum/go-ethereum/common"
7+
assert "github.com/stretchr/testify/require"
8+
)
9+
10+
func TestGetDestination(t *testing.T) {
11+
registerTokenPayload := "00a736aa000000000000774667629726ec1fabebcec0d9139bd1c8f72a2300e87648170000000000000000000000"
12+
decodePayloadAndCompareDestinationAddress(t, registerTokenPayload, "") // register token does not have a destination
13+
14+
sendTokenPayload := "00a736aa000000000001774667629726ec1fabebcec0d9139bd1c8f72a23008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4800c16ff2862300000000000000000000e87648170000000000000000000000"
15+
bobAddress := "0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"
16+
decodePayloadAndCompareDestinationAddress(t, sendTokenPayload, bobAddress)
17+
18+
sendTokenToPayload := "00a736aa000000000001774667629726ec1fabebcec0d9139bd1c8f72a2301d00700001cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c00286bee000000000000000000000000000064a7b3b6e00d000000000000000000e87648170000000000000000000000"
19+
ferdieAddress := "0x1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c"
20+
decodePayloadAndCompareDestinationAddress(t, sendTokenToPayload, ferdieAddress)
21+
22+
sendNativeTokenPayload := "00a736aa0000000000022121cfe35065c0c33465fbada265f08e9613428a4b9eb4bb717cd7db2abf622e008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48065cd1d00000000000000000000000000e87648170000000000000000000000"
23+
decodePayloadAndCompareDestinationAddress(t, sendNativeTokenPayload, bobAddress)
24+
}
25+
26+
func decodePayloadAndCompareDestinationAddress(t *testing.T, payload, expectedAddress string) {
27+
data := gethCommon.Hex2Bytes(payload)
28+
29+
destination, err := GetDestination(data)
30+
assert.NoError(t, err)
31+
32+
assert.Equal(t, expectedAddress, destination)
33+
}

relayer/config/config.go

+12
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ type EthereumConfig struct {
1818
GasLimit uint64 `mapstructure:"gas-limit"`
1919
}
2020

21+
type OFACConfig struct {
22+
Enabled bool `mapstructure:"enabled"`
23+
ApiKey string `mapstructure:"apiKey"`
24+
}
25+
2126
func (p ParachainConfig) Validate() error {
2227
if p.Endpoint == "" {
2328
return errors.New("[endpoint] is not set")
@@ -41,3 +46,10 @@ func (p PolkadotConfig) Validate() error {
4146
}
4247
return nil
4348
}
49+
50+
func (o OFACConfig) Validate() error {
51+
if o.Enabled && o.ApiKey == "" {
52+
return errors.New("OFAC is enabled but no [apiKey] set")
53+
}
54+
return nil
55+
}

relayer/ofac/ofac.go

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package ofac
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io"
7+
"net/http"
8+
9+
log "github.com/sirupsen/logrus"
10+
)
11+
12+
type OFAC struct {
13+
enabled bool
14+
apiKey string
15+
}
16+
17+
type Response struct {
18+
Identifications []struct {
19+
Category string `json:"category"`
20+
Name string `json:"name"`
21+
Description string `json:"description"`
22+
URL string `json:"url"`
23+
} `json:"identifications"`
24+
}
25+
26+
func New(enabled bool, apiKey string) *OFAC {
27+
return &OFAC{enabled, apiKey}
28+
}
29+
30+
func (o OFAC) IsBanned(source, destination string) (bool, error) {
31+
if !o.enabled {
32+
return false, nil
33+
}
34+
35+
if source != "" {
36+
isSourcedBanned, err := o.isOFACListed(source)
37+
if err != nil {
38+
return true, err
39+
}
40+
if isSourcedBanned {
41+
log.WithField("source", source).Warn("found ofac banned source address")
42+
return true, nil
43+
}
44+
}
45+
46+
if destination != "" {
47+
isDestinationBanned, err := o.isOFACListed(destination)
48+
if err != nil {
49+
return true, err
50+
}
51+
if isDestinationBanned {
52+
log.WithField("destination", destination).Warn("found ofac banned destination address")
53+
return true, nil
54+
}
55+
}
56+
57+
return false, nil
58+
}
59+
60+
func (o OFAC) isOFACListed(address string) (bool, error) {
61+
client := &http.Client{}
62+
63+
req, err := http.NewRequest("GET", fmt.Sprintf("https://public.chainalysis.com/api/v1/address/%s", address), nil)
64+
if err != nil {
65+
return true, err
66+
}
67+
68+
req.Header.Add("Accept", "application/json")
69+
req.Header.Add("X-API-Key", o.apiKey)
70+
71+
resp, err := client.Do(req)
72+
if err != nil {
73+
return true, err
74+
}
75+
defer resp.Body.Close()
76+
77+
body, err := io.ReadAll(resp.Body)
78+
if err != nil {
79+
return true, err
80+
}
81+
82+
var response Response
83+
err = json.Unmarshal(body, &response)
84+
if err != nil {
85+
return true, err
86+
}
87+
88+
return len(response.Identifications) > 0, nil
89+
}

relayer/relays/execution/config.go

+11-5
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import (
99
)
1010

1111
type Config struct {
12-
Source SourceConfig `mapstructure:"source"`
13-
Sink SinkConfig `mapstructure:"sink"`
14-
InstantVerification bool `mapstructure:"instantVerification"`
15-
Schedule ScheduleConfig `mapstructure:"schedule"`
12+
Source SourceConfig `mapstructure:"source"`
13+
Sink SinkConfig `mapstructure:"sink"`
14+
InstantVerification bool `mapstructure:"instantVerification"`
15+
Schedule ScheduleConfig `mapstructure:"schedule"`
16+
OFAC config.OFACConfig `mapstructure:"ofac"`
1617
}
1718

1819
type ScheduleConfig struct {
@@ -46,7 +47,8 @@ type ContractsConfig struct {
4647
}
4748

4849
type SinkConfig struct {
49-
Parachain beaconconf.ParachainConfig `mapstructure:"parachain"`
50+
Parachain beaconconf.ParachainConfig `mapstructure:"parachain"`
51+
SS58Prefix uint8 `mapstructure:"ss58Prefix"`
5052
}
5153

5254
type ChannelID [32]byte
@@ -70,5 +72,9 @@ func (c Config) Validate() error {
7072
if err != nil {
7173
return fmt.Errorf("schedule config: %w", err)
7274
}
75+
err = c.OFAC.Validate()
76+
if err != nil {
77+
return fmt.Errorf("ofac config: %w", err)
78+
}
7379
return nil
7480
}

0 commit comments

Comments
 (0)