Skip to content

Commit c46f871

Browse files
author
dev-warrior777
committed
client: Allow Firo to send to an EXX address.
EXX addresses are used for Firo and other privacy coins to send funds to Binance and other exchanges due to last US administration regulations.
1 parent 0030e49 commit c46f871

File tree

10 files changed

+194
-12
lines changed

10 files changed

+194
-12
lines changed

client/asset/btc/btc.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,10 @@ type BTCCloneCFG struct {
364364
// into an address string. If AddressStringer is not supplied, the
365365
// (btcutil.Address).String method will be used.
366366
AddressStringer dexbtc.AddressStringer // btcutil.Address => string, may be an override or just the String method
367+
// PayToAddressScript is an optional argument that can make non-standard tx
368+
// outputs. If PayToAddressScript is not supplied the (txscript).PayToAddrScript
369+
// method will be used. Note the extra paramaeter for a string address.
370+
PayToAddressScript func(btcutil.Address, string) ([]byte, error)
367371
// BlockDeserializer can be used in place of (*wire.MsgBlock).Deserialize.
368372
BlockDeserializer func([]byte) (*wire.MsgBlock, error)
369373
// ArglessChangeAddrRPC can be true if the getrawchangeaddress takes no
@@ -801,6 +805,7 @@ type baseWallet struct {
801805
localFeeRate func(context.Context, RawRequester, uint64) (uint64, error)
802806
feeCache *feeRateCache
803807
decodeAddr dexbtc.AddressDecoder
808+
payToAddress func(btcutil.Address, string) ([]byte, error)
804809
walletDir string
805810

806811
deserializeTx func([]byte) (*wire.MsgTx, error)
@@ -1317,6 +1322,13 @@ func newUnconnectedWallet(cfg *BTCCloneCFG, walletCfg *WalletConfig) (*baseWalle
13171322
}
13181323
}
13191324

1325+
addressPayer := cfg.PayToAddressScript
1326+
if addressPayer == nil {
1327+
addressPayer = func(addr btcutil.Address, _ string) ([]byte, error) {
1328+
return txscript.PayToAddrScript(addr)
1329+
}
1330+
}
1331+
13201332
w := &baseWallet{
13211333
symbol: cfg.Symbol,
13221334
chainParams: cfg.ChainParams,
@@ -1336,6 +1348,7 @@ func newUnconnectedWallet(cfg *BTCCloneCFG, walletCfg *WalletConfig) (*baseWalle
13361348
feeCache: feeCache,
13371349
decodeAddr: addrDecoder,
13381350
stringAddr: addrStringer,
1351+
payToAddress: addressPayer,
13391352
walletInfo: cfg.WalletInfo,
13401353
deserializeTx: txDeserializer,
13411354
serializeTx: txSerializer,
@@ -4505,7 +4518,7 @@ func (btc *baseWallet) send(address string, val uint64, feeRate uint64, subtract
45054518
if err != nil {
45064519
return nil, 0, 0, fmt.Errorf("invalid address: %s", address)
45074520
}
4508-
pay2script, err := txscript.PayToAddrScript(addr)
4521+
pay2script, err := btc.payToAddress(addr, address)
45094522
if err != nil {
45104523
return nil, 0, 0, fmt.Errorf("PayToAddrScript error: %w", err)
45114524
}

client/asset/firo/exx.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package firo
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/btcsuite/btcd/btcutil"
8+
"github.com/btcsuite/btcd/chaincfg"
9+
"github.com/btcsuite/btcd/txscript"
10+
"github.com/btcsuite/btcutil/base58"
11+
)
12+
13+
const (
14+
PKH_LEN = 20
15+
MAINNET_VER_BYTE_PKH = 0x52
16+
MAINNET_VER_BYTE_EXX = 0x01
17+
TESTNET_VER_BYTE_PKH = 0x41
18+
TESTNET_VER_BYTE_EXT = 0x01
19+
ALLNETS_EXTRA_BYTE_EX_ONE = 0xb9 // python: ADDRTYPE_EXP2PKH
20+
MAINNET_EXTRA_BYTE_EXX_TWO = 0xbb
21+
TESTNET_EXTRA_BYTE_EXT_TWO = 0xb1
22+
23+
EXCHANGE_MARKER_BYTE = 0xe0 // python: OP_EXCHANGEADDR
24+
SCRIPT_LEN = 26
25+
)
26+
27+
func isExxAddress(address string) bool {
28+
return strings.HasPrefix(address, "EXX") || strings.HasPrefix(address, "EXT")
29+
}
30+
31+
// decodeExxAddress decodes a Firo exchange address.
32+
func decodeExxAddress(encodedAddr string, net *chaincfg.Params) (btcutil.Address, error) {
33+
decoded, _, err := base58.CheckDecode(encodedAddr)
34+
if err != nil {
35+
return nil, err
36+
}
37+
// pull out the pkh - Mainnet: (ver=0x01) <0xb9> <0xbb> <PKH>
38+
decLen := len(decoded)
39+
if decLen < PKH_LEN+1 {
40+
return nil, fmt.Errorf("bad decoded len %d", decLen)
41+
}
42+
decExtra := decLen - PKH_LEN
43+
pkh := decoded[decExtra:]
44+
45+
addrPKH, err := btcutil.NewAddressPubKeyHash(pkh, net)
46+
if err != nil {
47+
return nil, err
48+
}
49+
return btcutil.Address(addrPKH), nil
50+
}
51+
52+
func buildExxPayToScript(addr btcutil.Address, address string) ([]byte, error) {
53+
if _, isPKH := addr.(*btcutil.AddressPubKeyHash); !isPKH {
54+
return nil, fmt.Errorf("address %s does not contain a pubkey hash", address)
55+
}
56+
baseScript, err := txscript.PayToAddrScript(addr)
57+
if err != nil {
58+
return nil, err
59+
}
60+
script := make([]byte, 0, len(baseScript)+1)
61+
script = append(script, EXCHANGE_MARKER_BYTE)
62+
script = append(script, baseScript...)
63+
return script, nil
64+
}

client/asset/firo/exx_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package firo
2+
3+
import (
4+
"bytes"
5+
"encoding/hex"
6+
"fmt"
7+
"testing"
8+
9+
dexfiro "decred.org/dcrdex/dex/networks/firo"
10+
"github.com/btcsuite/btcd/btcutil"
11+
)
12+
13+
const (
14+
exxAddress = "EXXKcAcVWXeG7S9aiXXGuGNZkWdB9XuSbJ1z"
15+
decoded = "386ed39285803b1782d0e363897f1a81a5b87421"
16+
// 386ed39285803b1782d0e363897f1a81a5b87421
17+
// e0 76a914 386ed39285803b1782d0e363897f1a81a5b87421 88ac
18+
// validateaddress firo-qt, electrum-firo just says 'true'
19+
encodedAsPKH = "a5rrM1DY9XTRucbNrJQDtDc6GiEbcX7jRd"
20+
)
21+
22+
func TestDecodeExxAddress(t *testing.T) {
23+
addr, err := decodeExxAddress(exxAddress, dexfiro.MainNetParams)
24+
if err != nil {
25+
t.Fatalf("addr=%v - %v", addr, err)
26+
}
27+
28+
switch ty := addr.(type) {
29+
case btcutil.Address:
30+
fmt.Printf("type=%T\n", ty)
31+
default:
32+
t.Fatalf("invalid type=%T", ty)
33+
}
34+
35+
if !addr.IsForNet(dexfiro.MainNetParams) {
36+
t.Fatalf("IsForNet failed")
37+
}
38+
decodedB, err := hex.DecodeString(decoded)
39+
if err != nil {
40+
t.Fatalf("hex decode error: %v", err)
41+
}
42+
if !bytes.Equal(addr.ScriptAddress(), decodedB) {
43+
t.Fatalf("ScriptAddress failed")
44+
}
45+
s := addr.String()
46+
if s != encodedAsPKH {
47+
t.Fatalf("String failed expected %s got %s", encodedAsPKH, s)
48+
}
49+
}
50+
51+
func TestBuildExxPayToScript(t *testing.T) {
52+
addr, err := decodeExxAddress(exxAddress, dexfiro.MainNetParams)
53+
if err != nil {
54+
t.Fatalf("addr=%v - %v", addr, err)
55+
}
56+
script, err := buildExxPayToScript(addr, exxAddress)
57+
if err != nil {
58+
t.Fatal(err)
59+
}
60+
if len(script) != SCRIPT_LEN {
61+
t.Fatalf("wrong script length - expected %d got %d", SCRIPT_LEN, len(script))
62+
}
63+
}

client/asset/firo/firo.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/btcsuite/btcd/btcec/v2"
2323
"github.com/btcsuite/btcd/btcutil"
2424
"github.com/btcsuite/btcd/chaincfg"
25+
"github.com/btcsuite/btcd/txscript"
2526
)
2627

2728
const (
@@ -168,6 +169,8 @@ func NewWallet(cfg *asset.WalletConfig, logger dex.Logger, network dex.Network)
168169
AssetID: BipID,
169170
FeeEstimator: estimateFee,
170171
ExternalFeeEstimator: externalFeeRate,
172+
AddressDecoder: decodeAddress,
173+
PayToAddressScript: payToAddressScript,
171174
PrivKeyFunc: nil, // set only for walletTypeRPC below
172175
}
173176

@@ -195,6 +198,35 @@ func NewWallet(cfg *asset.WalletConfig, logger dex.Logger, network dex.Network)
195198
}
196199
}
197200

201+
/******************************************************************************
202+
Helper Functions
203+
******************************************************************************/
204+
205+
// decodeAddress decodes a firo address. For normal transparent addresses this
206+
// just uses btcd: btcutil.DecodeAddress.
207+
func decodeAddress(address string, net *chaincfg.Params) (btcutil.Address, error) {
208+
if isExxAddress(address) {
209+
return decodeExxAddress(address, net)
210+
}
211+
decAddr, err := btcutil.DecodeAddress(address, net)
212+
if err != nil {
213+
return nil, err
214+
}
215+
if !decAddr.IsForNet(net) {
216+
return nil, errors.New("wrong network")
217+
}
218+
return decAddr, nil
219+
}
220+
221+
// payToAddressScript builds a P2PKH script for a Firo output. For normal transparent
222+
// addresses btcd: txscript.PayToAddrScript is used.
223+
func payToAddressScript(addr btcutil.Address, address string) ([]byte, error) {
224+
if isExxAddress(address) {
225+
return buildExxPayToScript(addr, address)
226+
}
227+
return txscript.PayToAddrScript(addr)
228+
}
229+
198230
// rpcCaller is satisfied by ExchangeWalletFullNode (baseWallet), providing
199231
// direct RPC requests.
200232
type rpcCaller interface {

dex/testing/firo/README_ELECTRUM_HARNESSES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ See Also: README_HARNESS.md
1212
## 2. ElectrumX-Firo Test Harness
1313

1414
The harness is a script named **electrumx.sh** which downloads a git repo
15-
containing a release version of ElectrumX-Firo server.
15+
containing a specific commit of ElectrumX-Firo server. No external releases.
1616

1717
It requires **harness.sh** Firo chain server harness running.
1818

dex/testing/firo/electrum.sh

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python
2222
SCRIPT_DIR=$(pwd)
2323

2424
# Electrum-Firo Version 4.1.5.2
25-
COMMIT=a3f64386efc9069cae83e23c241331de6f418b2f
25+
# COMMIT=a3f64386efc9069cae83e23c241331de6f418b2f
26+
27+
# Electrum-Firo Version 4.1.5.5
28+
COMMIT=b99e9594bddeecba82a2531bbf0769bd589f3a34
2629

2730
GENESIS=a42b98f04cc2916e8adfb5d9db8a2227c4629bc205748ed2f33180b636ee885b # regtest
2831
RPCPORT=8001
@@ -56,15 +59,12 @@ fi
5659

5760
git remote -v
5861

59-
CURRENT_COMMIT=$(git rev-parse HEAD)
60-
if [ ! "${CURRENT_COMMIT}" == "${COMMIT}" ]; then
61-
git fetch --depth 1 origin ${COMMIT}
62-
git reset --hard FETCH_HEAD
63-
fi
62+
git fetch --depth 1 origin ${COMMIT}
63+
git reset --hard FETCH_HEAD
6464

6565
if [ ! -d "${ELECTRUM_DIR}/venv" ]; then
6666
# The venv interpreter will be this python version, e.g. python3.10
67-
python3 -m venv ${ELECTRUM_DIR}/venv
67+
python3.7 -m venv ${ELECTRUM_DIR}/venv
6868
fi
6969
source ${ELECTRUM_DIR}/venv/bin/activate
7070
python --version

dex/testing/firo/electrumx.sh

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
1616

1717
set -ex
1818

19-
COMMIT=c0cdcc0dfcaa057058fd1ed281557dede924cd27
19+
# No external releases - just master
20+
# May 11, 2023
21+
# COMMIT=c0cdcc0dfcaa057058fd1ed281557dede924cd27
22+
# Jul 8, 2024
23+
COMMIT=937e4bb3d8802317b64231844b698d8758029ca5
2024

2125
ELECTRUMX_DIR=~/dextest/electrum/firo/server
2226
REPO_DIR=${ELECTRUMX_DIR}/electrumx-repo
@@ -53,7 +57,8 @@ export NET="regtest"
5357
export DB_ENGINE="leveldb"
5458
export DB_DIRECTORY="${DATA_DIR}"
5559
export DAEMON_URL="http://user:[email protected]:53768" # harness:alpha:rpc
56-
export SERVICES="ssl://localhost:50002,rpc://"
60+
# export SERVICES="ssl://localhost:50002,rpc://"
61+
export SERVICES="tcp://localhost:50001,rpc://"
5762
export SSL_CERTFILE="${DATA_DIR}/ssl.cert"
5863
export SSL_KEYFILE="${DATA_DIR}/ssl.key"
5964
export PEER_DISCOVERY="off"

dex/testing/firo/harness.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ cd ${NODES_ROOT} && tmux new-session -d -s $SESSION $SHELL
7676
# Write config files.
7777
################################################################################
7878
echo "Writing node config files"
79-
# These config files aren't actually used here, but can be used by other programs.
79+
# These config files aren't actually used here, but can be used by other programs
80+
# such as loadbot.
8081

8182
cat > "${ALPHA_DIR}/alpha.conf" <<EOF
8283
rpcuser=user

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ require (
8484
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
8585
github.com/beorn7/perks v1.0.1 // indirect
8686
github.com/bits-and-blooms/bitset v1.10.0 // indirect
87+
github.com/btcsuite/btcutil v1.0.2
8788
github.com/btcsuite/btcwallet/wallet/txrules v1.2.1 // indirect
8889
github.com/btcsuite/btcwallet/wallet/txsizes v1.2.4 // indirect
8990
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtyd
145145
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
146146
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
147147
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
148+
github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts=
149+
github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts=
148150
github.com/btcsuite/btcwallet v0.16.10-0.20240815225602-6ecae9c12fde h1:NURMAR/mat4V2ZwZE/dXIu+pfHc3SKN/kvAcKlfs/CU=
149151
github.com/btcsuite/btcwallet v0.16.10-0.20240815225602-6ecae9c12fde/go.mod h1:X2xDre+j1QphTRo54y2TikUzeSvreL1t1aMXrD8Kc5A=
150152
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.4 h1:poyHFf7+5+RdxNp5r2T6IBRD7RyraUsYARYbp/7t4D8=
@@ -1188,6 +1190,7 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U
11881190
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
11891191
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
11901192
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
1193+
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
11911194
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
11921195
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
11931196
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=

0 commit comments

Comments
 (0)