From 0d2d75b7f39f1b6815041efd6316d59659a94251 Mon Sep 17 00:00:00 2001 From: Brian Stafford Date: Sun, 3 Apr 2022 12:29:24 -0500 Subject: [PATCH] add support for ZCash Implement block deserialization, tx deserialization/serialization and input signing for ZCash and generalize those functions in the client and server btc packages. Implemenation notes 1. zcashd does not support encrypted wallets. No passwords allowed. 2. After starting the harness, it takes a few minutes for beta to get caught up. 3. zcashd can take a very long time to get it's fee estimates primed. --- client/asset/bch/bch.go | 48 +-- client/asset/btc/btc.go | 250 +++++++++--- client/asset/btc/livetest/livetest.go | 53 ++- client/asset/btc/rpcclient.go | 88 +++-- client/asset/ltc/regnet_test.go | 5 +- client/asset/zec/regnet_test.go | 42 ++ client/asset/zec/sign.go | 259 ++++++++++++ client/asset/zec/sign_test.go | 93 +++++ client/asset/zec/zec.go | 255 ++++++++++++ client/cmd/dexc/main.go | 1 + client/webserver/site/src/img/coins/zec.png | Bin 0 -> 4061 bytes client/webserver/site/src/js/doc.ts | 3 +- dex/networks/zec/addr.go | 83 ++++ dex/networks/zec/addr_test.go | 30 ++ dex/networks/zec/block.go | 125 ++++++ dex/networks/zec/block_test.go | 191 +++++++++ dex/networks/zec/params.go | 86 ++++ dex/networks/zec/tx.go | 417 ++++++++++++++++++++ dex/networks/zec/tx_test.go | 143 +++++++ dex/testing/dcrdex/harness.sh | 31 +- dex/testing/zec/alphawallet | 127 ++++++ dex/testing/zec/betawallet | 116 ++++++ dex/testing/zec/harness.sh | 232 +++++++++++ go.mod | 1 + go.sum | 2 + run_tests.sh | 2 + server/asset/btc/btc.go | 86 ++-- server/asset/btc/cache.go | 3 +- server/asset/btc/live_test.go | 26 +- server/asset/btc/rpcclient.go | 46 ++- server/asset/btc/tx.go | 56 ++- server/asset/zec/live_test.go | 81 ++++ server/asset/zec/zec.go | 146 +++++++ server/asset/zec/zec_test.go | 41 ++ server/cmd/dcrdex/main.go | 1 + server/cmd/dexcoin/main.go | 1 + 36 files changed, 2945 insertions(+), 225 deletions(-) create mode 100644 client/asset/zec/regnet_test.go create mode 100644 client/asset/zec/sign.go create mode 100644 client/asset/zec/sign_test.go create mode 100644 client/asset/zec/zec.go create mode 100644 client/webserver/site/src/img/coins/zec.png create mode 100644 dex/networks/zec/addr.go create mode 100644 dex/networks/zec/addr_test.go create mode 100644 dex/networks/zec/block.go create mode 100644 dex/networks/zec/block_test.go create mode 100644 dex/networks/zec/params.go create mode 100644 dex/networks/zec/tx.go create mode 100644 dex/networks/zec/tx_test.go create mode 100644 dex/testing/zec/alphawallet create mode 100644 dex/testing/zec/betawallet create mode 100644 dex/testing/zec/harness.sh create mode 100644 server/asset/zec/live_test.go create mode 100644 server/asset/zec/zec.go create mode 100644 server/asset/zec/zec_test.go diff --git a/client/asset/bch/bch.go b/client/asset/bch/bch.go index 09a6ede4b5..f5d877a520 100644 --- a/client/asset/bch/bch.go +++ b/client/asset/bch/bch.go @@ -18,6 +18,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" "github.com/gcash/bchd/bchec" bchscript "github.com/gcash/bchd/txscript" bchwire "github.com/gcash/bchd/wire" @@ -162,59 +163,24 @@ func NewWallet(cfg *asset.WalletConfig, logger dex.Logger, network dex.Network) // Bitcoin Cash uses the Cash Address encoding, which is Bech32, but // not indicative of segwit. We provide a custom encoder. AddressDecoder: dexbch.DecodeCashAddress, + AddressStringer: func(addr btcutil.Address) string { + a, _ := dexbch.RecodeCashAddress(addr.String(), params) + return a + }, // Bitcoin Cash has a custom signature hash algorithm. Since they don't // have segwit, Bitcoin Cash implemented a variation of the withdrawn // BIP0062 that utilizes Shnorr signatures. // https://gist.github.com/markblundeberg/a3aba3c9d610e59c3c49199f697bc38b#making-unmalleable-smart-contracts // https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki NonSegwitSigner: rawTxInSigner, - // The old allowHighFees bool argument to sendrawtransaction. - ArglessChangeAddrRPC: true, + OmitAddressType: true, // Bitcoin Cash uses estimatefee instead of estimatesmartfee, and even // then, they modified it from the old Bitcoin Core estimatefee by // removing the confirmation target argument. FeeEstimator: estimateFee, } - xcWallet, err := btc.BTCCloneWallet(cloneCFG) - if err != nil { - return nil, err - } - - return &BCHWallet{ - ExchangeWalletFullNode: xcWallet, - }, nil -} - -// BCHWallet embeds btc.ExchangeWalletFullNode, but re-implements a couple of -// methods to perform on-the-fly address translation. -type BCHWallet struct { - *btc.ExchangeWalletFullNode -} - -// Address converts the Bitcoin base58-encoded address returned by the embedded -// ExchangeWallet into a Cash Address. -func (bch *BCHWallet) Address() (string, error) { - btcAddrStr, err := bch.ExchangeWalletFullNode.Address() - if err != nil { - return "", err - } - return dexbch.RecodeCashAddress(btcAddrStr, bch.Net()) -} - -// AuditContract modifies the *asset.Contract returned by the ExchangeWallet -// AuditContract method by converting the Recipient to the Cash Address -// encoding. -func (bch *BCHWallet) AuditContract(coinID, contract, txData dex.Bytes, rebroadcast bool) (*asset.AuditInfo, error) { // AuditInfo has address - ai, err := bch.ExchangeWalletFullNode.AuditContract(coinID, contract, txData, rebroadcast) - if err != nil { - return nil, err - } - ai.Recipient, err = dexbch.RecodeCashAddress(ai.Recipient, bch.Net()) - if err != nil { - return nil, err - } - return ai, nil + return btc.BTCCloneWallet(cloneCFG) } // rawTxSigner signs the transaction using Bitcoin Cash's custom signature diff --git a/client/asset/btc/btc.go b/client/asset/btc/btc.go index 74617e811e..42ca214546 100644 --- a/client/asset/btc/btc.go +++ b/client/asset/btc/btc.go @@ -214,6 +214,9 @@ type BTCCloneCFG struct { // LegacyBalance is for clones that don't yet support the 'getbalances' RPC // call. LegacyBalance bool + // ZECStyleBalance is for clones that don't support getbalances or + // walletinfo, and don't take an account name argument. + ZECStyleBalance bool // If segwit is false, legacy addresses and contracts will be used. This // setting must match the configuration of the server's asset backend. Segwit bool @@ -224,9 +227,6 @@ type BTCCloneCFG struct { // into btcutil.Address. If AddressDecoder is not supplied, // btcutil.DecodeAddress will be used. AddressDecoder dexbtc.AddressDecoder - // ArglessChangeAddrRPC can be true if the getrawchangeaddress takes no - // address-type argument. - ArglessChangeAddrRPC bool // NonSegwitSigner can be true if the transaction signature hash data is not // the standard for non-segwit Bitcoin. If nil, txscript. NonSegwitSigner TxInSigner @@ -242,9 +242,28 @@ type BTCCloneCFG struct { // BooleanGetBlockRPC causes the RPC client to use a boolean second argument // for the getblock endpoint, instead of Bitcoin's numeric. BooleanGetBlockRPC bool + // NumericGetRawRPC uses a numeric boolean indicator for the + // getrawtransaction RPC. + NumericGetRawRPC bool + // LegacyValidateAddressRPC uses the validateaddress endpoint instead of + // getwalletinfo in order to discover ownership of an address. + LegacyValidateAddressRPC bool // SingularWallet signals that the node software supports only one wallet, // so the RPC endpoint does not have a /wallet/{walletname} path. SingularWallet bool + // TxDeserializer is an optional function used to deserialize a transaction. + TxDeserializer func([]byte) (*wire.MsgTx, error) + // TxSerializer is an optional function used to serialize a transaction. + TxSerializer func(*wire.MsgTx) ([]byte, error) + // BlockDeserializer is an optional function to deserialize a block. + BlockDeserializer func([]byte) (*wire.MsgBlock, error) + // TxHasher is a function that generates a tx hash from a MsgTx. + TxHasher func(*wire.MsgTx) *chainhash.Hash + // AddressStringer is a function to convert an address to a string. + AddressStringer func(btcutil.Address) string + // TxSizeCalculator is an optional function that will be used to calculate + // the size of a transaction. + TxSizeCalculator func(*wire.MsgTx) uint64 } // outPoint is the hash and output index of a transaction output. @@ -437,7 +456,9 @@ func readRPCWalletConfig(settings map[string]string, symbol string, net dex.Netw // parseRPCWalletConfig parses a *RPCWalletConfig from the settings map and // creates the unconnected *rpcclient.Client. -func parseRPCWalletConfig(settings map[string]string, symbol string, net dex.Network, ports dexbtc.NetPorts, singularWallet bool) (*RPCWalletConfig, *rpcclient.Client, error) { +func parseRPCWalletConfig(settings map[string]string, symbol string, net dex.Network, + ports dexbtc.NetPorts, singularWallet bool) (*RPCWalletConfig, *rpcclient.Client, error) { + cfg, err := readRPCWalletConfig(settings, symbol, net, ports) if err != nil { return nil, nil, err @@ -567,11 +588,17 @@ type baseWallet struct { redeemConfTarget uint64 useSplitTx bool useLegacyBalance bool + zecStyleBalance bool segwit bool legacyRawFeeLimit bool // wut dis? signNonSegwit TxInSigner estimateFee func(RawRequester, uint64) (uint64, error) decodeAddr dexbtc.AddressDecoder + deserializeTx func([]byte) (*wire.MsgTx, error) + serializeTx func(*wire.MsgTx) ([]byte, error) + calcTxSize func(*wire.MsgTx) uint64 + hashTx func(*wire.MsgTx) *chainhash.Hash + stringifyAddress func(btcutil.Address) string tipMtx sync.RWMutex currentTip *block @@ -729,35 +756,36 @@ func BTCCloneWallet(cfg *BTCCloneCFG) (*ExchangeWalletFullNode, error) { return btc, nil } -func newRPCWalletConnection(cfg *RPCWalletConfig) (*rpcclient.Client, error) { - endpoint := cfg.RPCBind + "/wallet/" + cfg.WalletName - return rpcclient.New(&rpcclient.ConnConfig{ - HTTPPostMode: true, - DisableTLS: true, - Host: endpoint, - User: cfg.RPCUser, - Pass: cfg.RPCPass, - }, nil) -} - // newRPCWallet creates the ExchangeWallet and starts the block monitor. func newRPCWallet(requester RawRequesterWithContext, cfg *BTCCloneCFG, walletConfig *WalletConfig) (*ExchangeWalletFullNode, error) { btc, err := newUnconnectedWallet(cfg, walletConfig) if err != nil { return nil, err } + + blockDeserializer := cfg.BlockDeserializer + if blockDeserializer == nil { + blockDeserializer = deserializeBlock + } + btc.node = newRPCClient(&rpcCore{ - requester: requester, - segwit: cfg.Segwit, - decodeAddr: btc.decodeAddr, - arglessChangeAddrRPC: cfg.ArglessChangeAddrRPC, - legacyRawSends: cfg.LegacyRawFeeLimit, - minNetworkVersion: cfg.MinNetworkVersion, - log: cfg.Logger.SubLogger("RPC"), - chainParams: cfg.ChainParams, - omitAddressType: cfg.OmitAddressType, - legacySignTx: cfg.LegacySignTxRPC, - booleanGetBlock: cfg.BooleanGetBlockRPC, + requester: requester, + segwit: cfg.Segwit, + decodeAddr: btc.decodeAddr, + legacyRawSends: cfg.LegacyRawFeeLimit, + minNetworkVersion: cfg.MinNetworkVersion, + log: cfg.Logger.SubLogger("RPC"), + chainParams: cfg.ChainParams, + omitAddressType: cfg.OmitAddressType, + legacySignTx: cfg.LegacySignTxRPC, + booleanGetBlock: cfg.BooleanGetBlockRPC, + deserializeTx: btc.deserializeTx, + serializeTx: btc.serializeTx, + deserializeBlock: blockDeserializer, + hashTx: btc.hashTx, + numericGetRawTxRPC: cfg.NumericGetRawRPC, + legacyValidateAddressRPC: cfg.LegacyValidateAddressRPC, + stringifyAddress: btc.stringifyAddress, }) return &ExchangeWalletFullNode{btc}, nil } @@ -803,6 +831,31 @@ func newUnconnectedWallet(cfg *BTCCloneCFG, walletCfg *WalletConfig) (*baseWalle nonSegwitSigner = cfg.NonSegwitSigner } + txDeserializer := cfg.TxDeserializer + if txDeserializer == nil { + txDeserializer = msgTxFromBytes + } + + txSerializer := cfg.TxSerializer + if txSerializer == nil { + txSerializer = serializeMsgTx + } + + txSizeCalculator := cfg.TxSizeCalculator + if txSizeCalculator == nil { + txSizeCalculator = dexbtc.MsgTxVBytes + } + + txHasher := cfg.TxHasher + if txHasher == nil { + txHasher = hashTx + } + + addressStringer := cfg.AddressStringer + if addressStringer == nil { + addressStringer = stringifyAddress + } + w := &baseWallet{ symbol: cfg.Symbol, chainParams: cfg.ChainParams, @@ -817,12 +870,18 @@ func newUnconnectedWallet(cfg *BTCCloneCFG, walletCfg *WalletConfig) (*baseWalle redeemConfTarget: redeemConfTarget, useSplitTx: walletCfg.UseSplitTx, useLegacyBalance: cfg.LegacyBalance, + zecStyleBalance: cfg.ZECStyleBalance, segwit: cfg.Segwit, legacyRawFeeLimit: cfg.LegacyRawFeeLimit, signNonSegwit: nonSegwitSigner, estimateFee: cfg.FeeEstimator, decodeAddr: addrDecoder, walletInfo: cfg.WalletInfo, + deserializeTx: txDeserializer, + serializeTx: txSerializer, + hashTx: txHasher, + stringifyAddress: addressStringer, + calcTxSize: txSizeCalculator, } if w.estimateFee == nil { @@ -923,10 +982,21 @@ func (btc *baseWallet) shutdown() { // getBlockchainInfoResult models the data returned from the getblockchaininfo // command. type getBlockchainInfoResult struct { - Blocks int64 `json:"blocks"` - Headers int64 `json:"headers"` - BestBlockHash string `json:"bestblockhash"` - InitialBlockDownload bool `json:"initialblockdownload"` + Blocks int64 `json:"blocks"` + Headers int64 `json:"headers"` + BestBlockHash string `json:"bestblockhash"` + InitialBlockDownload *bool `json:"initialblockdownload"` + InitialBlockDownloadComplete *bool `json:"initial_block_download_complete"` +} + +func (r *getBlockchainInfoResult) syncing() bool { + if r.InitialBlockDownloadComplete != nil && *r.InitialBlockDownloadComplete { + return false + } + if r.InitialBlockDownload != nil && *r.InitialBlockDownload { + return true + } + return r.Headers-r.Blocks > 1 } // SyncStatus is information about the blockchain sync status. @@ -969,7 +1039,7 @@ func (btc *baseWallet) OwnsAddress(address string) (bool, error) { // Balance returns the total available funds in the wallet. Part of the // asset.Wallet interface. func (btc *baseWallet) Balance() (*asset.Balance, error) { - if btc.useLegacyBalance { + if btc.useLegacyBalance || btc.zecStyleBalance { return btc.legacyBalance() } balances, err := btc.node.balances() @@ -996,6 +1066,15 @@ func (btc *baseWallet) legacyBalance() (*asset.Balance, error) { return nil, fmt.Errorf("legacyBalance unimplemented for spv clients") } + if btc.zecStyleBalance { + var bal float64 + // args: "(dummy)" minconf includeWatchonly inZat + if err := cl.call(methodGetBalance, anylist{"", 0, false, true}, &bal); err != nil { + return nil, err + } + return &asset.Balance{Available: toSatoshi(bal)}, nil + } + walletInfo, err := cl.GetWalletInfo() if err != nil { return nil, fmt.Errorf("(legacy) GetWalletInfo error: %w", err) @@ -1691,15 +1770,15 @@ func (btc *baseWallet) split(value uint64, lots uint64, outputs []*output, if err != nil { return nil, false, err } - txHash := msgTx.TxHash() - op := newOutput(&txHash, 0, reqFunds) + txHash := btc.hashTx(msgTx) + op := newOutput(txHash, 0, reqFunds) // Need to save one funding coin (in the deferred function). fundingCoins = map[outPoint]*utxo{op.pt: { txHash: op.txHash(), vout: op.vout(), - address: addr.String(), + address: btc.stringifyAddress(addr), amount: reqFunds, }} @@ -1973,12 +2052,12 @@ func (btc *baseWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, ui if err != nil { return nil, nil, 0, err } + txHash := btc.hashTx(msgTx) // Prepare the receipts. receipts := make([]asset.Receipt, 0, swapCount) - txHash := msgTx.TxHash() for i, contract := range swaps.Contracts { - output := newOutput(&txHash, uint32(i), contract.Value) + output := newOutput(txHash, uint32(i), contract.Value) signedRefundTx, err := btc.refundTx(output.txHash(), output.vout(), contracts[i], contract.Value, refundAddrs[i], swaps.FeeRate) if err != nil { return nil, nil, 0, fmt.Errorf("error creating refund tx: %w", err) @@ -2024,7 +2103,7 @@ func (btc *baseWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, ui btc.fundingCoins[change.pt] = &utxo{ txHash: change.txHash(), vout: change.vout(), - address: changeAddr.String(), + address: btc.stringifyAddress(changeAddr), amount: change.value, } } @@ -2075,7 +2154,7 @@ func (btc *baseWallet) Redeem(form *asset.RedeemForm) ([]dex.Bytes, asset.Coin, } // Calculate the size and the fees. - size := dexbtc.MsgTxVBytes(msgTx) + size := btc.calcTxSize(msgTx) if btc.segwit { // Add the marker and flag weight here. witnessVBytes := (dexbtc.RedeemSwapSigScriptSize*uint64(len(form.Redemptions)) + 2 + 3) / 4 @@ -2147,12 +2226,12 @@ func (btc *baseWallet) Redeem(form *asset.RedeemForm) ([]dex.Bytes, asset.Coin, } // Send the transaction. - checkHash := msgTx.TxHash() + checkHash := btc.hashTx(msgTx) txHash, err := btc.node.sendRawTransaction(msgTx) if err != nil { return nil, nil, 0, err } - if *txHash != checkHash { + if *txHash != *checkHash { return nil, nil, 0, fmt.Errorf("redemption sent, but received unexpected transaction ID back from RPC server. "+ "expected %s, got %s", *txHash, checkHash) } @@ -2254,7 +2333,7 @@ func (btc *baseWallet) AuditContract(coinID, contract, txData dex.Bytes, rebroad return nil, fmt.Errorf("error finding unspent contract: %s:%d : %w", txHash, vout, err) } } else { - tx, err = msgTxFromBytes(txData) + tx, err = btc.deserializeTx(txData) if err != nil { return nil, fmt.Errorf("coin not found, and error encountered decoding tx data: %v", err) } @@ -2314,7 +2393,7 @@ func (btc *baseWallet) AuditContract(coinID, contract, txData dex.Bytes, rebroad return &asset.AuditInfo{ Coin: newOutput(txHash, vout, uint64(txOut.Value)), - Recipient: receiver.String(), + Recipient: btc.stringifyAddress(receiver), Contract: contract, SecretHash: secretHash, Expiration: time.Unix(int64(stamp), 0).UTC(), @@ -2348,7 +2427,11 @@ func (btc *ExchangeWalletFullNode) AuditContract(coinID, contract, txData dex.By return btc.baseWallet.AuditContract(coinID, contract, txData, rebroadcast) } - txData, _ = serializeMsgTx(tx) // if error, we'll just pass nil and let it try + txData, err = btc.serializeTx(tx) // if error, we'll just pass nil and let it try + if err != nil { + return nil, err + } + return btc.baseWallet.AuditContract(coinID, contract, txData, rebroadcast) } @@ -2395,7 +2478,7 @@ func (btc *baseWallet) FindRedemption(ctx context.Context, coinID, _ dex.Bytes) return nil, nil, fmt.Errorf("error finding wallet transaction: %v", err) } - txOut, err := txOutFromTxBytes(tx.Hex, vout) + txOut, err := btc.txOutFromTxBytes(tx.Hex, vout) if err != nil { return nil, nil, err } @@ -2645,12 +2728,12 @@ func (btc *baseWallet) Refund(coinID, contract dex.Bytes, feeSuggestion uint64) return nil, fmt.Errorf("error creating refund tx: %w", err) } - checkHash := msgTx.TxHash() + checkHash := btc.hashTx(msgTx) refundHash, err := btc.node.sendRawTransaction(msgTx) if err != nil { return nil, fmt.Errorf("sendRawTransaction: %w", err) } - if *refundHash != checkHash { + if *refundHash != *checkHash { return nil, fmt.Errorf("refund sent, but received unexpected transaction ID back from RPC server. "+ "expected %s, got %s", *refundHash, checkHash) } @@ -2678,7 +2761,7 @@ func (btc *baseWallet) refundTx(txHash *chainhash.Hash, vout uint32, contract de msgTx.AddTxIn(txIn) // Calculate fees and add the change output. - size := dexbtc.MsgTxVBytes(msgTx) + size := btc.calcTxSize(msgTx) if btc.segwit { // Add the marker and flag weight too. @@ -2736,7 +2819,7 @@ func (btc *baseWallet) Address() (string, error) { if err != nil { return "", err } - return addr.String(), nil + return btc.stringifyAddress(addr), nil } // NewAddress returns a new address from the wallet. This satisfies the @@ -2807,7 +2890,7 @@ func (btc *baseWallet) send(address string, val uint64, feeRate uint64, subtract return nil, 0, 0, fmt.Errorf("failed to fetch transaction after send: %w", err) } - tx, err := msgTxFromBytes(txRes.Hex) + tx, err := btc.deserializeTx(txRes.Hex) if err != nil { return nil, 0, 0, fmt.Errorf("error decoding transaction: %w", err) } @@ -3136,13 +3219,13 @@ func (btc *baseWallet) checkRedemptionBlockDetails(outPt outPoint, blockHash *ch } blk, err := btc.node.getBlock(*blockHash) if err != nil { - return 0, fmt.Errorf("error retrieving redemption block for %s: %w", blockHash, err) + return 0, fmt.Errorf("error retrieving redemption block %s: %w", blockHash, err) } var tx *wire.MsgTx out: for _, iTx := range blk.Transactions { - if iTx.TxHash() == outPt.txHash { + if *btc.hashTx(iTx) == outPt.txHash { tx = iTx break out } @@ -3211,7 +3294,7 @@ func (btc *baseWallet) signTxAndAddChange(baseTx *wire.MsgTx, addr btcutil.Addre if err != nil { return makeErr("signing error: %v, raw tx: %x", err, btc.wireBytes(baseTx)) } - vSize := dexbtc.MsgTxVBytes(msgTx) + vSize := btc.calcTxSize(msgTx) minFee := feeRate * vSize remaining := totalIn - totalOut if minFee > remaining { @@ -3238,10 +3321,10 @@ func (btc *baseWallet) signTxAndAddChange(baseTx *wire.MsgTx, addr btcutil.Addre changeAdded := !dexbtc.IsDust(changeOutput, feeRate) if changeAdded { // Add the change output. - vSize0 := dexbtc.MsgTxVBytes(baseTx) + vSize0 := btc.calcTxSize(baseTx) baseTx.AddTxOut(changeOutput) - changeSize := dexbtc.MsgTxVBytes(baseTx) - vSize0 // may be dexbtc.P2WPKHOutputSize - btc.log.Debugf("Change output size = %d, addr = %s", changeSize, addr.String()) + changeSize := btc.calcTxSize(baseTx) - vSize0 // may be dexbtc.P2WPKHOutputSize + btc.log.Debugf("Change output size = %d, addr = %s", changeSize, btc.stringifyAddress(addr)) vSize += changeSize fee := feeRate * vSize @@ -3255,7 +3338,7 @@ func (btc *baseWallet) signTxAndAddChange(baseTx *wire.MsgTx, addr btcutil.Addre if err != nil { return makeErr("signing error: %v, raw tx: %x", err, btc.wireBytes(baseTx)) } - vSize = dexbtc.MsgTxVBytes(msgTx) // recompute the size with new tx signature + vSize = btc.calcTxSize(msgTx) // recompute the size with new tx signature reqFee := feeRate * vSize if reqFee > remaining { // I can't imagine a scenario where this condition would be true, but @@ -3289,16 +3372,17 @@ func (btc *baseWallet) signTxAndAddChange(baseTx *wire.MsgTx, addr btcutil.Addre totalOut += uint64(changeOutput.Value) } + txHash := btc.hashTx(msgTx) + fee := totalIn - totalOut actualFeeRate := fee / vSize - txHash := msgTx.TxHash() btc.log.Debugf("%d signature cycles to converge on fees for tx %s: "+ "min rate = %d, actual fee rate = %d (%v for %v bytes), change = %v", sigCycles, txHash, feeRate, actualFeeRate, fee, vSize, changeAdded) var change *output if changeAdded { - change = newOutput(&txHash, uint32(changeIdx), uint64(changeOutput.Value)) + change = newOutput(txHash, uint32(changeIdx), uint64(changeOutput.Value)) } return msgTx, change, fee, nil @@ -3309,25 +3393,41 @@ func (btc *baseWallet) broadcastTx(signedTx *wire.MsgTx) error { if err != nil { return fmt.Errorf("sendrawtx error: %v, raw tx: %x", err, btc.wireBytes(signedTx)) } - checkHash := signedTx.TxHash() - if *txHash != checkHash { + checkHash := btc.hashTx(signedTx) + if *txHash != *checkHash { return fmt.Errorf("transaction sent, but received unexpected transaction ID back from RPC server. "+ "expected %s, got %s. raw tx: %x", checkHash, *txHash, btc.wireBytes(signedTx)) } return nil } +// txOutFromTxBytes parses the specified *wire.TxOut from the serialized +// transaction. +func (btc *baseWallet) txOutFromTxBytes(txB []byte, vout uint32) (*wire.TxOut, error) { + msgTx, err := btc.deserializeTx(txB) + if err != nil { + return nil, fmt.Errorf("error decoding transaction bytes: %v", err) + } + + if len(msgTx.TxOut) <= int(vout) { + return nil, fmt.Errorf("no vout %d in tx %s", vout, btc.hashTx(msgTx)) + } + return msgTx.TxOut[vout], nil +} + // createSig creates and returns the serialized raw signature and compressed // pubkey for a transaction input signature. func (btc *baseWallet) createSig(tx *wire.MsgTx, idx int, pkScript []byte, addr btcutil.Address, val uint64) (sig, pubkey []byte, err error) { - privKey, err := btc.node.privKeyForAddress(addr.String()) + privKey, err := btc.node.privKeyForAddress(btc.stringifyAddress(addr)) if err != nil { return nil, nil, err } + sig, err = btc.signNonSegwit(tx, idx, pkScript, txscript.SigHashAll, privKey, val) if err != nil { return nil, nil, err } + return sig, privKey.PubKey().SerializeCompressed(), nil } @@ -3336,7 +3436,7 @@ func (btc *baseWallet) createSig(tx *wire.MsgTx, idx int, pkScript []byte, addr func (btc *baseWallet) createWitnessSig(tx *wire.MsgTx, idx int, pkScript []byte, addr btcutil.Address, val uint64, sigHashes *txscript.TxSigHashes) (sig, pubkey []byte, err error) { - privKey, err := btc.node.privKeyForAddress(addr.String()) + privKey, err := btc.node.privKeyForAddress(btc.stringifyAddress(addr)) if err != nil { return nil, nil, err } @@ -3452,7 +3552,7 @@ func (btc *baseWallet) lockedSats() (uint64, error) { if err != nil { return 0, err } - txOut, err := txOutFromTxBytes(tx.Hex, rpcOP.Vout) + txOut, err := btc.txOutFromTxBytes(tx.Hex, rpcOP.Vout) if err != nil { return 0, err } @@ -3600,6 +3700,12 @@ func rawTxInSig(tx *wire.MsgTx, idx int, pkScript []byte, hashType txscript.SigH func findRedemptionsInTx(ctx context.Context, segwit bool, reqs map[outPoint]*findRedemptionReq, msgTx *wire.MsgTx, chainParams *chaincfg.Params) (discovered map[outPoint]*findRedemptionResult) { + return findRedemptionsInTxWithHasher(ctx, segwit, reqs, msgTx, chainParams, hashTx) +} + +func findRedemptionsInTxWithHasher(ctx context.Context, segwit bool, reqs map[outPoint]*findRedemptionReq, msgTx *wire.MsgTx, + chainParams *chaincfg.Params, hashTx func(*wire.MsgTx) *chainhash.Hash) (discovered map[outPoint]*findRedemptionResult) { + discovered = make(map[outPoint]*findRedemptionResult, len(reqs)) for vin, txIn := range msgTx.TxIn { @@ -3613,7 +3719,7 @@ func findRedemptionsInTx(ctx context.Context, segwit bool, reqs map[outPoint]*fi } if outPt.txHash == poHash && outPt.vout == poVout { // Match! - txHash := msgTx.TxHash() + txHash := hashTx(msgTx) secret, err := dexbtc.FindKeyPush(txIn.Witness, txIn.SignatureScript, req.contractHash[:], segwit, chainParams) if err != nil { req.fail("no secret extracted from redemption input %s:%d for swap output %s: %v", @@ -3621,7 +3727,7 @@ func findRedemptionsInTx(ctx context.Context, segwit bool, reqs map[outPoint]*fi continue } discovered[outPt] = &findRedemptionResult{ - redemptionCoinID: toCoinID(&txHash, uint32(vin)), + redemptionCoinID: toCoinID(txHash, uint32(vin)), secret: secret, } } @@ -3659,3 +3765,17 @@ func float64PtrStr(v *float64) string { } return strconv.FormatFloat(*v, 'f', 8, 64) } + +func hashTx(tx *wire.MsgTx) *chainhash.Hash { + h := tx.TxHash() + return &h +} + +func stringifyAddress(addr btcutil.Address) string { + return addr.String() +} + +func deserializeBlock(b []byte) (*wire.MsgBlock, error) { + msgBlock := &wire.MsgBlock{} + return msgBlock, msgBlock.Deserialize(bytes.NewReader(b)) +} diff --git a/client/asset/btc/livetest/livetest.go b/client/asset/btc/livetest/livetest.go index 12ed233160..98297d0272 100644 --- a/client/asset/btc/livetest/livetest.go +++ b/client/asset/btc/livetest/livetest.go @@ -37,23 +37,29 @@ var tLogger dex.Logger type WalletConstructor func(cfg *asset.WalletConfig, logger dex.Logger, network dex.Network) (asset.Wallet, error) -func tBackend(ctx context.Context, t *testing.T, cfg *Config, node, name string, blkFunc func(string, error)) *connectedWallet { +func tBackend(ctx context.Context, t *testing.T, cfg *Config, walletName *WalletName, blkFunc func(string, error)) *connectedWallet { t.Helper() user, err := user.Current() if err != nil { t.Fatalf("error getting current user: %v", err) } - cfgPath := filepath.Join(user.HomeDir, "dextest", cfg.Asset.Symbol, node, node+".conf") + + fileName := walletName.Filename + if fileName == "" { + fileName = walletName.Node + ".conf" + } + + cfgPath := filepath.Join(user.HomeDir, "dextest", cfg.Asset.Symbol, walletName.Node, fileName) settings, err := config.Parse(cfgPath) if err != nil { t.Fatalf("error reading config options: %v", err) } - settings["walletname"] = name + settings["walletname"] = walletName.Name if cfg.SplitTx { settings["txsplit"] = "1" } - reportName := fmt.Sprintf("%s:%s-%s", cfg.Asset.Symbol, node, name) + reportName := fmt.Sprintf("%s:%s-%s", cfg.Asset.Symbol, walletName.Node, walletName.Name) walletCfg := &asset.WalletConfig{ Settings: settings, @@ -65,7 +71,7 @@ func tBackend(ctx context.Context, t *testing.T, cfg *Config, node, name string, }, } - w, err := cfg.NewWallet(walletCfg, tLogger.SubLogger(node+"."+name), dex.Regtest) + w, err := cfg.NewWallet(walletCfg, tLogger.SubLogger(walletName.Node+"."+walletName.Name), dex.Regtest) if err != nil { t.Fatalf("error creating backend: %v", err) } @@ -121,6 +127,9 @@ func randBytes(l int) []byte { type WalletName struct { Node string Name string + // Filename is optional. If specified, it will be used instead of + // [node].conf. + Filename string } type Config struct { @@ -131,6 +140,7 @@ type Config struct { SPV bool FirstWallet *WalletName SecondWallet *WalletName + Unencrypted bool } func Run(t *testing.T, cfg *Config) { @@ -179,24 +189,26 @@ func Run(t *testing.T, cfg *Config) { } t.Log("Setting up alpha/beta/gamma wallet backends...") - rig.firstWallet = tBackend(tCtx, t, cfg, cfg.FirstWallet.Node, cfg.FirstWallet.Name, blkFunc) + rig.firstWallet = tBackend(tCtx, t, cfg, cfg.FirstWallet, blkFunc) // rig.backends["beta"], rig.connectionMasters["beta"] = tBackend(tCtx, t, cfg, "beta", "", tLogger.SubLogger("beta"), blkFunc) - rig.secondWallet = tBackend(tCtx, t, cfg, cfg.SecondWallet.Node, cfg.SecondWallet.Name, blkFunc) + rig.secondWallet = tBackend(tCtx, t, cfg, cfg.SecondWallet, blkFunc) defer rig.close() // Unlock the wallet for use. - err := rig.firstWallet.Unlock(walletPassword) - if err != nil { - t.Fatalf("error unlocking gamma wallet: %v", err) - } + if !cfg.Unencrypted { + err := rig.firstWallet.Unlock(walletPassword) + if err != nil { + t.Fatalf("error unlocking gamma wallet: %v", err) + } - if cfg.SPV { - // // The test expects beta and gamma to be unlocked. - // if err := rig.beta().Unlock(walletPassword); err != nil { - // t.Fatalf("beta Unlock error: %v", err) - // } - if err := rig.secondWallet.Unlock(walletPassword); err != nil { - t.Fatalf("gamma Unlock error: %v", err) + if cfg.SPV { + // // The test expects beta and gamma to be unlocked. + // if err := rig.beta().Unlock(walletPassword); err != nil { + // t.Fatalf("beta Unlock error: %v", err) + // } + if err := rig.secondWallet.Unlock(walletPassword); err != nil { + t.Fatalf("gamma Unlock error: %v", err) + } } } @@ -365,12 +377,15 @@ func Run(t *testing.T, cfg *Config) { if strings.Contains(err.Error(), "error finding unspent contract") { return wait.TryAgain } + c <- nil t.Fatalf("error auditing contract: %v", err) } c <- ai return wait.DontTryAgain }, - ExpireFunc: func() { t.Fatalf("makeRedemption -> AuditContract timed out") }, + ExpireFunc: func() { + t.Fatalf("makeRedemption -> AuditContract timed out") + }, }) // Alpha should be able to redeem. diff --git a/client/asset/btc/rpcclient.go b/client/asset/btc/rpcclient.go index 47aecae9fb..9f86d92beb 100644 --- a/client/asset/btc/rpcclient.go +++ b/client/asset/btc/rpcclient.go @@ -25,6 +25,7 @@ import ( const ( methodGetBalances = "getbalances" + methodGetBalance = "getbalance" methodListUnspent = "listunspent" methodLockUnspent = "lockunspent" methodListLockUnspent = "listlockunspent" @@ -40,6 +41,7 @@ const ( methodSetTxFee = "settxfee" methodGetWalletInfo = "getwalletinfo" methodGetAddressInfo = "getaddressinfo" + methodValidateAddress = "validateaddress" methodEstimateSmartFee = "estimatesmartfee" methodSendRawTransaction = "sendrawtransaction" methodGetTxOut = "gettxout" @@ -70,17 +72,23 @@ type RawRequesterWithContext interface { type anylist []interface{} type rpcCore struct { - requester RawRequesterWithContext - segwit bool - decodeAddr dexbtc.AddressDecoder - arglessChangeAddrRPC bool - legacyRawSends bool - minNetworkVersion uint64 - log dex.Logger - chainParams *chaincfg.Params - omitAddressType bool - legacySignTx bool - booleanGetBlock bool + requester RawRequesterWithContext + segwit bool + decodeAddr dexbtc.AddressDecoder + legacyRawSends bool + minNetworkVersion uint64 + log dex.Logger + chainParams *chaincfg.Params + omitAddressType bool + legacySignTx bool + booleanGetBlock bool + deserializeTx func([]byte) (*wire.MsgTx, error) + serializeTx func(*wire.MsgTx) ([]byte, error) + deserializeBlock func([]byte) (*wire.MsgBlock, error) + hashTx func(*wire.MsgTx) *chainhash.Hash + numericGetRawTxRPC bool + legacyValidateAddressRPC bool + stringifyAddress func(btcutil.Address) string } // rpcClient is a bitcoind JSON RPC client that uses rpcclient.Client's @@ -134,7 +142,7 @@ func (wc *rpcClient) estimateSmartFee(confTarget int64, mode *btcjson.EstimateSm // SendRawTransactionLegacy broadcasts the transaction with an additional legacy // boolean `allowhighfees` argument set to false. func (wc *rpcClient) SendRawTransactionLegacy(tx *wire.MsgTx) (*chainhash.Hash, error) { - txBytes, err := serializeMsgTx(tx) + txBytes, err := wc.serializeTx(tx) if err != nil { return nil, err } @@ -201,19 +209,19 @@ func (wc *rpcClient) callHashGetter(method string, args anylist) (*chainhash.Has // getBlock fetches the MsgBlock. func (wc *rpcClient) getBlock(h chainhash.Hash) (*wire.MsgBlock, error) { - var txB dex.Bytes + var blkB dex.Bytes args := anylist{h.String()} if wc.booleanGetBlock { args = append(args, false) } else { args = append(args, 0) } - err := wc.call(methodGetBlock, args, &txB) + err := wc.call(methodGetBlock, args, &blkB) if err != nil { return nil, err } - msgBlock := &wire.MsgBlock{} - return msgBlock, msgBlock.Deserialize(bytes.NewReader(txB)) + + return wc.deserializeBlock(blkB) } // getBlockHash returns the hash of the block in the best block chain at the @@ -264,12 +272,16 @@ func (wc *rpcClient) GetRawMempool() ([]*chainhash.Hash, error) { // GetRawTransaction retrieves the MsgTx. func (wc *rpcClient) GetRawTransaction(txHash *chainhash.Hash) (*wire.MsgTx, error) { var txB dex.Bytes - err := wc.call(methodGetRawTransaction, anylist{txHash.String(), false}, &txB) + args := anylist{txHash.String(), false} + if wc.numericGetRawTxRPC { + args[1] = 0 + } + err := wc.call(methodGetRawTransaction, args, &txB) if err != nil { return nil, err } - tx := wire.NewMsgTx(wire.TxVersion) - return tx, tx.Deserialize(bytes.NewReader(txB)) + + return wc.deserializeTx(txB) } // balances retrieves a wallet's balance details. @@ -318,7 +330,7 @@ func (wc *rpcClient) changeAddress() (btcutil.Address, error) { var addrStr string var err error switch { - case wc.arglessChangeAddrRPC: + case wc.omitAddressType: err = wc.call(methodChangeAddress, nil, &addrStr) case wc.segwit: err = wc.call(methodChangeAddress, anylist{"bech32"}, &addrStr) @@ -360,7 +372,7 @@ func (wc *rpcClient) address(aType string) (btcutil.Address, error) { // signTx attempts to have the wallet sign the transaction inputs. func (wc *rpcClient) signTx(inTx *wire.MsgTx) (*wire.MsgTx, error) { - txBytes, err := serializeMsgTx(inTx) + txBytes, err := wc.serializeTx(inTx) if err != nil { return nil, fmt.Errorf("tx serialization error: %w", err) } @@ -382,7 +394,7 @@ func (wc *rpcClient) signTx(inTx *wire.MsgTx) (*wire.MsgTx, error) { } return nil, fmt.Errorf("signing incomplete. %d signing errors encountered: %s", len(res.Errors), errMsg) } - outTx, err := msgTxFromBytes(res.Hex) + outTx, err := wc.deserializeTx(res.Hex) if err != nil { return nil, fmt.Errorf("error deserializing transaction response: %w", err) } @@ -455,14 +467,18 @@ func (wc *rpcClient) GetWalletInfo() (*GetWalletInfoResult, error) { // GetAddressInfo gets information about the given address by calling // getaddressinfo RPC command. -func (wc *rpcClient) getAddressInfo(addr btcutil.Address) (*GetAddressInfoResult, error) { +func (wc *rpcClient) getAddressInfo(addr btcutil.Address, method string) (*GetAddressInfoResult, error) { ai := new(GetAddressInfoResult) - return ai, wc.call(methodGetAddressInfo, anylist{addr.String()}, ai) + return ai, wc.call(method, anylist{wc.stringifyAddress(addr)}, ai) } // ownsAddress indicates if an address belongs to the wallet. func (wc *rpcClient) ownsAddress(addr btcutil.Address) (bool, error) { - ai, err := wc.getAddressInfo(addr) + method := methodGetAddressInfo + if wc.legacyValidateAddressRPC { + method = methodValidateAddress + } + ai, err := wc.getAddressInfo(addr, method) if err != nil { return false, err } @@ -485,13 +501,13 @@ func (wc *rpcClient) syncStatus() (*syncStatus, error) { return &syncStatus{ Target: int32(chainInfo.Headers), Height: int32(chainInfo.Blocks), - Syncing: chainInfo.InitialBlockDownload || chainInfo.Headers-chainInfo.Blocks > 1, + Syncing: chainInfo.syncing(), }, nil } // SendRawTransaction broadcasts the transaction. func (wc *rpcClient) SendRawTransaction(tx *wire.MsgTx) (*chainhash.Hash, error) { - b, err := serializeMsgTx(tx) + b, err := wc.serializeTx(tx) if err != nil { return nil, err } @@ -616,7 +632,7 @@ func (wc *rpcClient) findRedemptionsInMempool(ctx context.Context, reqs map[outP logAbandon(fmt.Sprintf("getrawtransaction error for tx hash %v: %v", txHash, err)) return } - newlyDiscovered := findRedemptionsInTx(ctx, wc.segwit, reqs, tx, wc.chainParams) + newlyDiscovered := findRedemptionsInTxWithHasher(ctx, wc.segwit, reqs, tx, wc.chainParams, wc.hashTx) for outPt, res := range newlyDiscovered { discovered[outPt] = res } @@ -637,7 +653,7 @@ func (wc *rpcClient) searchBlockForRedemptions(ctx context.Context, reqs map[out discovered = make(map[outPoint]*findRedemptionResult, len(reqs)) for _, msgTx := range msgBlock.Transactions { - newlyDiscovered := findRedemptionsInTx(ctx, wc.segwit, reqs, msgTx, wc.chainParams) + newlyDiscovered := findRedemptionsInTxWithHasher(ctx, wc.segwit, reqs, msgTx, wc.chainParams, wc.hashTx) for outPt, res := range newlyDiscovered { discovered[outPt] = res } @@ -696,17 +712,3 @@ func msgTxFromBytes(txB []byte) (*wire.MsgTx, error) { } return msgTx, nil } - -// txOutFromTxBytes parses the specified *wire.TxOut from the serialized -// transaction. -func txOutFromTxBytes(txB []byte, vout uint32) (*wire.TxOut, error) { - msgTx, err := msgTxFromBytes(txB) - if err != nil { - return nil, fmt.Errorf("error decoding transaction bytes: %v", err) - } - - if len(msgTx.TxOut) <= int(vout) { - return nil, fmt.Errorf("no vout %d in tx %s", vout, msgTx.TxHash()) - } - return msgTx.TxOut[vout], nil -} diff --git a/client/asset/ltc/regnet_test.go b/client/asset/ltc/regnet_test.go index b786f95ade..5101d5a9e2 100644 --- a/client/asset/ltc/regnet_test.go +++ b/client/asset/ltc/regnet_test.go @@ -25,9 +25,8 @@ import ( const alphaAddress = "mt9hgfXXbM3x7hewgEAovBwqoMAAnctJ4V" var ( - tLotSize uint64 = 1e6 - tRateStep uint64 = 10 - tLTC = &dex.Asset{ + tLotSize uint64 = 1e6 + tLTC = &dex.Asset{ ID: 2, Symbol: "ltc", Version: version, diff --git a/client/asset/zec/regnet_test.go b/client/asset/zec/regnet_test.go new file mode 100644 index 0000000000..7e3ff0caf9 --- /dev/null +++ b/client/asset/zec/regnet_test.go @@ -0,0 +1,42 @@ +//go:build harness + +package zec + +// Regnet tests expect the ZEC test harness to be running. + +import ( + "testing" + + "decred.org/dcrdex/client/asset/btc/livetest" + "decred.org/dcrdex/dex" + dexzec "decred.org/dcrdex/dex/networks/zec" +) + +var ( + tLotSize uint64 = 1e6 + tZEC = &dex.Asset{ + ID: BipID, + Symbol: "zec", + SwapSize: dexzec.InitTxSize, + SwapSizeBase: dexzec.InitTxSizeBase, + MaxFeeRate: 100, + SwapConf: 1, + } +) + +func TestWallet(t *testing.T) { + livetest.Run(t, &livetest.Config{ + NewWallet: NewWallet, + LotSize: tLotSize, + Asset: tZEC, + FirstWallet: &livetest.WalletName{ + Node: "alpha", + Filename: "zcash.conf", + }, + SecondWallet: &livetest.WalletName{ + Node: "beta", + Filename: "zcash.conf", + }, + Unencrypted: true, + }) +} diff --git a/client/asset/zec/sign.go b/client/asset/zec/sign.go new file mode 100644 index 0000000000..2543514839 --- /dev/null +++ b/client/asset/zec/sign.go @@ -0,0 +1,259 @@ +package zec + +import ( + "bytes" + "encoding/binary" + "fmt" + "math" + + dexzec "decred.org/dcrdex/dex/networks/zec" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/dchest/blake2b" +) + +const ( + // ZIP 143 personalization keys. + blake2BSigHash = "ZcashSigHash" + prevoutsHashPersonalization = "ZcashPrevoutHash" + sequenceHashPersonalization = "ZcashSequencHash" + outputsHashPersonalization = "ZcashOutputsHash" + + sigHashMask = 0x1f + + // versionOverwinterGroupID uint32 = 0x3C48270 + versionSaplingGroupID = 0x892f2085 + versionNU5GroupID = 0x26A7270A +) + +var ( + // Little-endian encoded concensus version IDs. + NoUpgrade = [4]byte{0x00, 0x00, 0x00, 0x00} // 0 + Overwinter = [4]byte{0x19, 0x1B, 0xA8, 0x5B} // 207500 + Sapling = [4]byte{0xBB, 0x09, 0xB8, 0x76} // 280000 + Blossom = [4]byte{0x60, 0x0E, 0xB4, 0x2B} // 653600 + Heartwood = [4]byte{0x0B, 0x23, 0xB9, 0xF5} // 903000 + Canopy = [4]byte{0xA6, 0x75, 0xFF, 0xE9} // 1046400 +) + +// blake2bSignatureHash creates a hash for a transparent input. +// This function will not work with transactions with shielded i/o. +// See https://github.com/zcash/librustzcash/blob/master/zcash_primitives/src/transaction/sighash_v4.rs +// Specifications: +// https://zips.z.cash/protocol/canopy.pdf section 4.9 points to +// https://github.com/zcash/zips/blob/main/zip-0243.rst#specification +// which extends https://zips.z.cash/zip-0143#specification +// See also implementation @ +// https://github.com/iqoption/zecutil/blob/master/sign.go +func blake2bSignatureHash( + subScript []byte, + sigHashes *txscript.TxSigHashes, + hashType txscript.SigHashType, + tx *dexzec.ZecTx, + idx int, + amt int64, + cver [4]byte, // consensus version (little-endian encoded uint32) +) (preimage, sHash []byte, err error) { + if tx.Version < dexzec.VersionSapling { + return nil, nil, fmt.Errorf("version %d transactions unsupported", tx.Version) + } + + if idx > len(tx.TxIn)-1 { + return nil, nil, fmt.Errorf("blake2bSignatureHash error: idx %d but %d txins", idx, len(tx.TxIn)) + } + + var sigHash bytes.Buffer + + // header is tx.Version with the overwintered flag set. + var bVersion [4]byte + binary.LittleEndian.PutUint32(bVersion[:], uint32(tx.Version)|(1<<31)) + sigHash.Write(bVersion[:]) + + // nVersionGroupId + var groupID uint32 = versionSaplingGroupID + if tx.Version == dexzec.VersionCanopy { + groupID = versionNU5GroupID + } + var nVersion [4]byte + binary.LittleEndian.PutUint32(nVersion[:], groupID) + sigHash.Write(nVersion[:]) + + var zeroHash chainhash.Hash + + // hashPrevouts + if hashType&txscript.SigHashAnyOneCanPay == 0 { + sigHash.Write(sigHashes.HashPrevOuts[:]) + } else { + sigHash.Write(zeroHash[:]) + } + + // hashSequence + if hashType&txscript.SigHashAnyOneCanPay == 0 && + hashType&sigHashMask != txscript.SigHashSingle && + hashType&sigHashMask != txscript.SigHashNone { + + sigHash.Write(sigHashes.HashSequence[:]) + } else { + sigHash.Write(zeroHash[:]) + } + + // hashOutputs + if hashType&sigHashMask != txscript.SigHashSingle && hashType&sigHashMask != txscript.SigHashNone { + sigHash.Write(sigHashes.HashOutputs[:]) + } else if hashType&sigHashMask == txscript.SigHashSingle && idx < len(tx.TxOut) { + var ( + b bytes.Buffer + h chainhash.Hash + ) + if err = wire.WriteTxOut(&b, 0, 0, tx.TxOut[idx]); err != nil { + return nil, nil, err + } + + if h, err = blake2bHash(b.Bytes(), []byte(outputsHashPersonalization)); err != nil { + return nil, nil, err + } + sigHash.Write(h.CloneBytes()) + } else { + sigHash.Write(zeroHash[:]) + } + + // hashJoinSplits + sigHash.Write(zeroHash[:]) + + // hashShieldedSpends + if tx.Version == dexzec.VersionSapling { + sigHash.Write(zeroHash[:]) + } + + // hashShieldedOutputs + if tx.Version == dexzec.VersionSapling { + sigHash.Write(zeroHash[:]) + } + + // nLockTime + var lockTime [4]byte + binary.LittleEndian.PutUint32(lockTime[:], tx.LockTime) + sigHash.Write(lockTime[:]) + + // nExpiryHeight + var expiryTime [4]byte + binary.LittleEndian.PutUint32(expiryTime[:], tx.ExpiryHeight) + sigHash.Write(expiryTime[:]) + + // valueBalance + if tx.Version >= dexzec.VersionSapling { + var valueBalance [8]byte + binary.LittleEndian.PutUint64(valueBalance[:], 0) + sigHash.Write(valueBalance[:]) + } + + // hash type + var bHashType [4]byte + binary.LittleEndian.PutUint32(bHashType[:], uint32(hashType)) + sigHash.Write(bHashType[:]) + + if idx != math.MaxUint32 { + // outpoint + sigHash.Write(tx.TxIn[idx].PreviousOutPoint.Hash[:]) + var bIndex [4]byte + binary.LittleEndian.PutUint32(bIndex[:], tx.TxIn[idx].PreviousOutPoint.Index) + sigHash.Write(bIndex[:]) + + // scriptCode + // There seems to be a long-standing bug in the ZCash Sighash digest + // code that adds the CompactSize encoding. We'll copy that bug here. + // This was noted also at https://github.com/zcash/zips/issues/196. + if err = wire.WriteVarBytes(&sigHash, 0, subScript); err != nil { + return nil, nil, err + } + + // value + if err = binary.Write(&sigHash, binary.LittleEndian, amt); err != nil { + return nil, nil, err + } + + // nSequence + var bSequence [4]byte + binary.LittleEndian.PutUint32(bSequence[:], tx.TxIn[idx].Sequence) + sigHash.Write(bSequence[:]) + } + + var h chainhash.Hash + preImage := sigHash.Bytes() + sigHashKey := append([]byte(blake2BSigHash), cver[:]...) + if h, err = blake2bHash(preImage, sigHashKey); err != nil { + return nil, nil, err + } + + return preImage, h.CloneBytes(), nil +} + +// blake2bHash is a BLAKE-2B has of the data with the specified personalization +// key. +func blake2bHash(data, personalizationKey []byte) (h chainhash.Hash, err error) { + bHash, err := blake2b.New(&blake2b.Config{Size: 32, Person: personalizationKey}) + if err != nil { + return h, err + } + + if _, err = bHash.Write(data); err != nil { + return h, err + } + + err = (&h).SetBytes(bHash.Sum(nil)) + return h, err +} + +// NewTxSigHashes is like btcd/txscript.NewTxSigHashes, except the underlying +// hash function is BLAKE-2B instead of double-SHA256. +func NewTxSigHashes(tx *dexzec.ZecTx) (h *txscript.TxSigHashes, err error) { + h = &txscript.TxSigHashes{} + if h.HashPrevOuts, err = calcHashPrevOuts(tx); err != nil { + return + } + if h.HashSequence, err = calcHashSequence(tx); err != nil { + return + } + if h.HashOutputs, err = calcHashOutputs(tx); err != nil { + return + } + return +} + +// calcHashPrevOuts is btcd/txscxript.calcHashPrevOuts, but with BLAKE-2B +// instead of double-SHA256. The personalization key is defined in ZIP 143. +func calcHashPrevOuts(tx *dexzec.ZecTx) (chainhash.Hash, error) { + var b bytes.Buffer + for _, in := range tx.TxIn { + b.Write(in.PreviousOutPoint.Hash[:]) + var buf [4]byte + binary.LittleEndian.PutUint32(buf[:], in.PreviousOutPoint.Index) + b.Write(buf[:]) + } + return blake2bHash(b.Bytes(), []byte(prevoutsHashPersonalization)) +} + +// calcHashSequence is btcd/txscxript.calcHashSequence, but with BLAKE-2B +// instead of double-SHA256. The personalization key is defined in ZIP 143. +func calcHashSequence(tx *dexzec.ZecTx) (chainhash.Hash, error) { + var b bytes.Buffer + for _, in := range tx.TxIn { + var buf [4]byte + binary.LittleEndian.PutUint32(buf[:], in.Sequence) + b.Write(buf[:]) + } + return blake2bHash(b.Bytes(), []byte(sequenceHashPersonalization)) +} + +// calcHashOutputs is btcd/txscxript.calcHashOutputs, but with BLAKE-2B +// instead of double-SHA256. The personalization key is defined in ZIP 143. +func calcHashOutputs(tx *dexzec.ZecTx) (_ chainhash.Hash, err error) { + var b bytes.Buffer + for _, out := range tx.TxOut { + if err = wire.WriteTxOut(&b, 0, 0, out); err != nil { + return chainhash.Hash{}, err + } + } + return blake2bHash(b.Bytes(), []byte(outputsHashPersonalization)) +} diff --git a/client/asset/zec/sign_test.go b/client/asset/zec/sign_test.go new file mode 100644 index 0000000000..0daa176149 --- /dev/null +++ b/client/asset/zec/sign_test.go @@ -0,0 +1,93 @@ +//go:build !harness + +package zec + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "fmt" + "testing" + + dexzec "decred.org/dcrdex/dex/networks/zec" + "github.com/btcsuite/btcd/txscript" +) + +func TestSign(t *testing.T) { + tests := []struct { + tx []byte + scriptCode []byte + expPreimage []byte + expSigHash []byte + consensusVersionID [4]byte + }{ + { // Test vector 3 from https://github.com/zcash/zips/blob/main/zip-0243.rst + tx: mustDecodeHex("0400008085202f8901a8c685478265f4c14dada651969c4" + + "5a65e1aeb8cd6791f2f5bb6a1d9952104d9010000006b483045022100a61e5d557568c" + + "2ddc1d9b03a7173c6ce7c996c4daecab007ac8f34bee01e6b9702204d38fdc0bcf2728" + + "a69fde78462a10fb45a9baa27873e6a5fc45fb5c76764202a01210365ffea3efa39089" + + "18a8b8627724af852fc9b86d7375b103ab0543cf418bcaa7ffeffffff02005a6202000" + + "000001976a9148132712c3ff19f3a151234616777420a6d7ef22688ac8b95980000000" + + "0001976a9145453e4698f02a38abdaa521cd1ff2dee6fac187188ac29b0040048b0040" + + "00000000000000000000000"), + scriptCode: mustDecodeHex("1976a914507173527b4c3318a2aecd793bf1cfed705950cf88ac"), + // This preimage is modified from the test vector to encode + // a CompactSize before the scriptCode + // See https://github.com/zcash/zips/issues/196 + expPreimage: mustDecodeHex("0400008085202f89fae31b8dec7b0b77e2c8d6b" + + "6eb0e7e4e55abc6574c26dd44464d9408a8e33f116c80d37f12d89b6f17ff198723e7d" + + "b1247c4811d1a695d74d930f99e98418790d2b04118469b7810a0d1cc59568320aad25" + + "a84f407ecac40b4f605a4e686845400000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000000000" + + "0000000000029b0040048b00400000000000000000001000000a8c685478265f4c14da" + + "da651969c45a65e1aeb8cd6791f2f5bb6a1d9952104d9010000001a1976a914507173527" + + "b4c3318a2aecd793bf1cfed705950cf88ac80f0fa0200000000feffffff"), + expSigHash: mustDecodeHex("f3148f80dfab5e573d5edfe7a850f5fd39234f80b5429d3a57edcc11e34c585b"), + consensusVersionID: Sapling, + }, + } + + for _, tt := range tests { + tx, err := dexzec.DeserializeZecTx(tt.tx) + if err != nil { + t.Fatalf("ZecTxFromBytes error: %v", err) + } + + cache, err := NewTxSigHashes(tx) + if err != nil { + t.Fatalf("NewTxSigHashes error: %v", err) + } + + amtB, _ := hex.DecodeString("80f0fa0200000000") + amt := binary.LittleEndian.Uint64(amtB) + + pimg, _, err := blake2bSignatureHash(tt.scriptCode, cache, txscript.SigHashAll, tx, 0, int64(amt), tt.consensusVersionID) + if err != nil { + t.Fatalf("blake2bSignatureHash error: %v", err) + } + + if !bytes.Equal(pimg, tt.expPreimage) { + fmt.Printf("expected preimage: %s \n", hex.EncodeToString(tt.expPreimage)) + fmt.Printf("calculated preimage: %s \n", hex.EncodeToString(pimg)) + t.Fatalf("wrong preimage") + } + + // Sighash from test vector will not be correct because of preimage error. + + // if !bytes.Equal(sigHash, tt.expSigHash) { + // fmt.Printf("expected sighash: %s \n", hex.EncodeToString(tt.expSigHash)) + // fmt.Printf("calculated sighash: %s \n", hex.EncodeToString(sigHash)) + // t.Fatalf("wrong sighash") + // } + } + +} + +func mustDecodeHex(hx string) []byte { + b, err := hex.DecodeString(hx) + if err != nil { + panic("mustDecodeHex: " + err.Error()) + } + return b +} diff --git a/client/asset/zec/zec.go b/client/asset/zec/zec.go new file mode 100644 index 0000000000..668b6bd05f --- /dev/null +++ b/client/asset/zec/zec.go @@ -0,0 +1,255 @@ +// This code is available on the terms of the project LICENSE.md file, +// also available online at https://blueoakcouncil.org/license/1.0.0. + +package zec + +import ( + "encoding/json" + "fmt" + "math" + + "decred.org/dcrdex/client/asset" + "decred.org/dcrdex/client/asset/btc" + "decred.org/dcrdex/dex" + dexbtc "decred.org/dcrdex/dex/networks/btc" + dexzec "decred.org/dcrdex/dex/networks/zec" + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" + dcrchaincfg "github.com/decred/dcrd/chaincfg/v3" +) + +const ( + version = 0 + BipID = 133 + // The default fee is passed to the user as part of the asset.WalletInfo + // structure. + defaultFee = 10 + defaultFeeRateLimit = 1000 + minNetworkVersion = 4060051 + walletTypeRPC = "zcashdRPC" +) + +var ( + fallbackFeeKey = "fallbackfee" + configOpts = []*asset.ConfigOption{ + { + Key: "rpcuser", + DisplayName: "JSON-RPC Username", + Description: "ZCash's 'rpcuser' setting", + }, + { + Key: "rpcpassword", + DisplayName: "JSON-RPC Password", + Description: "ZCash's 'rpcpassword' setting", + NoEcho: true, + }, + { + Key: "rpcbind", + DisplayName: "JSON-RPC Address", + Description: " or : (default 'localhost')", + }, + { + Key: "rpcport", + DisplayName: "JSON-RPC Port", + Description: "Port for RPC connections (if not set in Address)", + }, + { + Key: fallbackFeeKey, + DisplayName: "Fallback fee rate", + Description: "ZCash's 'fallbackfee' rate. Units: ZEC/kB", + DefaultValue: defaultFee * 1000 / 1e8, + }, + { + Key: "feeratelimit", + DisplayName: "Highest acceptable fee rate", + Description: "This is the highest network fee rate you are willing to " + + "pay on swap transactions. If feeratelimit is lower than a market's " + + "maxfeerate, you will not be able to trade on that market with this " + + "wallet. Units: BTC/kB", + DefaultValue: defaultFeeRateLimit * 1000 / 1e8, + }, + { + Key: "txsplit", + DisplayName: "Pre-split funding inputs", + Description: "When placing an order, create a \"split\" transaction to fund the order without locking more of the wallet balance than " + + "necessary. Otherwise, excess funds may be reserved to fund the order until the first swap contract is broadcast " + + "during match settlement, or the order is canceled. This an extra transaction for which network mining fees are paid. " + + "Used only for standing-type orders, e.g. limit orders without immediate time-in-force.", + IsBoolean: true, + }, + } + // WalletInfo defines some general information about a ZCash wallet. + WalletInfo = &asset.WalletInfo{ + Name: "ZCash", + Version: version, + UnitInfo: dexzec.UnitInfo, + AvailableWallets: []*asset.WalletDefinition{{ + Type: walletTypeRPC, + Tab: "External", + Description: "Connect to zcashcoind", + DefaultConfigPath: dexbtc.SystemConfigPath("zcash"), + ConfigOpts: configOpts, + }}, + } +) + +func init() { + asset.Register(BipID, &Driver{}) +} + +// Driver implements asset.Driver. +type Driver struct{} + +// Open creates the ZEC exchange wallet. Start the wallet with its Run method. +func (d *Driver) Open(cfg *asset.WalletConfig, logger dex.Logger, network dex.Network) (asset.Wallet, error) { + return NewWallet(cfg, logger, network) +} + +// DecodeCoinID creates a human-readable representation of a coin ID for +// ZCash. +func (d *Driver) DecodeCoinID(coinID []byte) (string, error) { + // ZCash and Bitcoin have the same tx hash and output format. + return (&btc.Driver{}).DecodeCoinID(coinID) +} + +// Info returns basic information about the wallet and asset. +func (d *Driver) Info() *asset.WalletInfo { + return WalletInfo +} + +// NewWallet is the exported constructor by which the DEX will import the +// exchange wallet. The wallet will shut down when the provided context is +// canceled. The configPath can be an empty string, in which case the standard +// system location of the zcashd config file is assumed. +func NewWallet(cfg *asset.WalletConfig, logger dex.Logger, network dex.Network) (asset.Wallet, error) { + var btcParams *chaincfg.Params + var addrParams *dcrchaincfg.Params + switch network { + case dex.Mainnet: + btcParams = dexzec.MainNetParams + addrParams = dexzec.MainNetAddressParams + case dex.Testnet: + btcParams = dexzec.TestNet4Params + addrParams = dexzec.TestNet4AddressParams + case dex.Regtest: + btcParams = dexzec.RegressionNetParams + addrParams = dexzec.RegressionNetAddressParams + default: + return nil, fmt.Errorf("unknown network ID %v", network) + } + + // Designate the clone ports. These will be overwritten by any explicit + // settings in the configuration file. + ports := dexbtc.NetPorts{ + Mainnet: "8232", + Testnet: "18232", + Simnet: "18232", + } + cloneCFG := &btc.BTCCloneCFG{ + WalletCFG: cfg, + MinNetworkVersion: minNetworkVersion, + WalletInfo: WalletInfo, + Symbol: "zec", + Logger: logger, + Network: network, + ChainParams: btcParams, + Ports: ports, + DefaultFallbackFee: defaultFee, + DefaultFeeRateLimit: defaultFeeRateLimit, + LegacyRawFeeLimit: true, + ZECStyleBalance: true, + Segwit: false, + OmitAddressType: true, + LegacySignTxRPC: true, + NumericGetRawRPC: true, + LegacyValidateAddressRPC: true, + SingularWallet: true, + FeeEstimator: estimateFee, + AddressDecoder: func(addr string, net *chaincfg.Params) (btcutil.Address, error) { + return dexzec.DecodeAddress(addr, addrParams, btcParams) + }, + AddressStringer: func(addr btcutil.Address) string { + s, _ := dexzec.RecodeAddress(addr.String(), addrParams, btcParams) + return s + }, + TxSizeCalculator: dexzec.CalcTxSize, + NonSegwitSigner: signTx, + TxDeserializer: func(b []byte) (*wire.MsgTx, error) { + zecTx, err := dexzec.DeserializeZecTx(b) + if err != nil { + return nil, err + } + return zecTx.MsgTx, nil + }, + BlockDeserializer: func(b []byte) (*wire.MsgBlock, error) { + zecBlock, err := dexzec.DeserializeZecBlock(b) + if err != nil { + return nil, err + } + return &zecBlock.MsgBlock, nil + }, + TxSerializer: func(btcTx *wire.MsgTx) ([]byte, error) { + return zecTx(btcTx).Bytes() + }, + TxHasher: func(tx *wire.MsgTx) *chainhash.Hash { + h := zecTx(tx).TxHash() + return &h + }, + } + + return btc.BTCCloneWallet(cloneCFG) +} + +func zecTx(tx *wire.MsgTx) *dexzec.ZecTx { + return dexzec.NewZecTxFromMsgTx(tx, dexzec.MaxExpiryHeight, dexzec.VersionSapling) +} + +// estimateFee uses ZCash's estimatefee RPC, since estimatesmartfee +// is not implemented. +// ZCash's fee estimation is pretty crappy. Full nodes can take hours to +// get up to speed, and forget about simnet. +// See https://github.com/zcash/zcash/issues/2552 +func estimateFee(node btc.RawRequester, confTarget uint64) (uint64, error) { + resp, err := node.RawRequest("estimatefee", nil) + if err != nil { + return 0, err + } + var feeRate float64 + err = json.Unmarshal(resp, &feeRate) + if err != nil { + return 0, err + } + if feeRate <= 0 { + return 0, fmt.Errorf("fee could not be estimated") + } + return uint64(math.Round(feeRate * 1e5)), nil +} + +// signTx signs the transaction input with ZCash's BLAKE-2B sighash digest. +// Won't work with shielded or blended transactions. +func signTx(btcTx *wire.MsgTx, idx int, pkScript []byte, hashType txscript.SigHashType, key *btcec.PrivateKey, amt uint64) ([]byte, error) { + tx := zecTx(btcTx) + cache, err := NewTxSigHashes(tx) + if err != nil { + return nil, fmt.Errorf("NewTxSigHashes error: %v", err) + } + + // Compare with zcash/zcash TransactionSignatureCreator::CreateSig + // also zcash_transaction_transparent_signature_digest() + + _, sigHash, err := blake2bSignatureHash(pkScript, cache, hashType, tx, idx, int64(amt), Canopy) + if err != nil { + return nil, fmt.Errorf("sighash calculation error: %v", err) + } + + signature, err := key.Sign(sigHash) + if err != nil { + return nil, fmt.Errorf("cannot sign tx input: %s", err) + } + + return append(signature.Serialize(), byte(hashType)), nil +} diff --git a/client/cmd/dexc/main.go b/client/cmd/dexc/main.go index 5322b6c4bc..d6092816eb 100644 --- a/client/cmd/dexc/main.go +++ b/client/cmd/dexc/main.go @@ -22,6 +22,7 @@ import ( _ "decred.org/dcrdex/client/asset/dcr" // register dcr asset _ "decred.org/dcrdex/client/asset/doge" // register doge asset _ "decred.org/dcrdex/client/asset/ltc" // register ltc asset + _ "decred.org/dcrdex/client/asset/zec" // register zec asset "decred.org/dcrdex/client/cmd/dexc/version" "decred.org/dcrdex/client/core" diff --git a/client/webserver/site/src/img/coins/zec.png b/client/webserver/site/src/img/coins/zec.png new file mode 100644 index 0000000000000000000000000000000000000000..34b150f5e735c75f7cb82de45d6b40c8a0985b1d GIT binary patch literal 4061 zcmV<34TS-JgRCwC$oq3oP#TmwbHM`J&3lakw4uKdkq6UaSjEE#CgC^hw z!6b^wsxdK&7dZ?bh$0Z9l7JdCXhe)x5O0Y%1PsIr69MB5qR4&7>B7#!PUVmKW@*OV zsp{F;ncmrbpQoSgnXT@r?zfKbs;{6Cp@OO>wTz%S4EQSWHQ-?25TG^C0w|V}dw?=v z8?Zr+<-iK1)Tdf&-AG7gXa{@;I05Jb^Z=UG@}4_^rNAQKW#C`HXN`TbX7mDXmY1e; za8v@X0oMWD8ejjuo^TEDevU{;cf1K)2^`cAiRc470#t;6h}2^jFbz1WArNsq5DOs* z)g1|7Ch(0yB;p9*$$COWb#u*hphF=L(Hs~Ll-E--{Krn<*Fcl{D)#mR?gtLf()|*^ z3OPOkHp#J5^bdfhKr5iF=qiWHacCBwbumy1yj5R%MKLfzx_WI|WEn6U_^puJq84>J z5I9Lh+9uW zU1?f!JVsOC`*P3k0d2C}i`N3r0keRAdQmFnO%$OS23!vu6Y!o}fwROMlXpTc0;U2* z0j~KF_>FY=RIE9R8q0QWgp6v~_v@^j$s z0QY!Z#_A7*h|a)7nOMVOb>0iy2VCz(*(Bff1|9^C3_!|o;OSfvG8}l+u|;d(4IH<|p8 zttUFwJfPHzvdTAK-sLFeZIyTReq9oBDDWTAf?X!e!6I@Jk5MWQ)1tO-Y1>Y?^NI9j zt~n1P*_n~H06+DjJSU$ANCP|2;o9XG%l5XqAfzdT&`=Y7PBQuBdJWvD0%yKKn_nr>3fFA@I zvQBm&V=0O|<@~x#$S5G31o{_pJ{DMQDL<4Nq9to066XP-3 zVqyWVbx<@%xe{^&#^0O{F%kDrri(Uhke@jyfn!Cg z>IaEwgr^}yQB+-_6ub<);*+Zzu@A+{1doGa7RIiZ4i-W* z1F%~#r%3NO7GvWo7-T8R>fCsYj%X?|?gpFo-YwmbHOz8@O^CCJ`ftJA50{<}M|H`{ zt(pKyiR%GOP+mF=tCkRn!n4f>4+DF|l6!WckkRXK7=0cT7iI01=Sx7}7L1WK9fs^T z=2eYW34Jv72=AFbIl4tQYTm6gygNO6FL!$x480cC1&Ae_d^xmjomGxIh%WacCiY-x znzDT`VNaitqT-)q`H>AOqF&Jn4JODTD`<6qIkEw*;ZJ`LM#j`Dp-AonA*p-x z4;vZf6>!6Suy(8aHGlmbw5$7o)&42(u14`!DI;_z2|3!Lo$pXeEtm6eg^3Ie+&>#0 zn&*Da?Z1S6J#tdLx7VGI1?R`>GlTEqB@pn2|Qz^?IFmHxjx z2qA~s=1lq9IV@yU?1Aw!;Da^p*G&5@bpA?CtJz$mJ2(<@h@TLf<1*=R=@J$~9-RmG zJnvrqrcyY$ckb({%(6RxjX?;p#i=Hfw6qEX8B3PKMYp+^AJhvjISV5BS@e)x;K+p>V{|JWkrCuRP$X-X5cjv33 z?|uU3PH->Z?`RnH!>lez$hc$sdw{K6#PLO!IF5sLg6(B6>P}_bxDc1iDFaXvnDWDb=>L z9UJ|G*bETe0GbVA9d0XzYCrJqhO4K-j&k>Frv3`rv<_9K)sL!DcYtjW{m4&IAg$a|!>*B_)4x{I7Is_UnizZkB$ z&%ONlr@)9)2qTj3>R5};SB_#u>ZHEba`R*~{KC3?f>j&gqFda{cWnn_O2KOqNC78ql>F})oL#inDxaHzQ(N(Lf?#M>XmoA5UXBQM13E>;5)#MkT40m5 zl8IEt(TM$w44AAI=t_Mm_C)?OQopE9dz@yui2}w*FL@qV(8xm-9`&LuQNJOi!N``}ZU{aUc6|9n?^FQNh_{4DsY+B5pWVeJExu?y(! zMR^Ysw7wX7oLgT^E*W=1R75GA(68fLOi}}$i4;f9OzchQc#P|EA$jE?31&K+??tP! z#l4JuxyI9&1ZX%9Spbqn^D5H&NdQhy>`ja`FG7t^Rdfm*cM2JZxN9RLjwLUQtqr}5 zNwaOktw7(V1GUEF@L1FU$atQ8i9v_rmVjrHD1#XjlANS$Q7InAy4l%_vI~=m^t}Qm zVc)~%6iPA$3hyuUI0#QHi#XVShcyBZJunn1^d$IK!kwF zPU@0azL!+At(fTt6v#w>H*mTar3|xh=_TxuB*}SrjO&jCjbS0(z_ z%vDNjeJv)>R;@FNY~2YHEdBMCi?f!DzZ|&2i}Dxw+#R!foXbX&>%_#gwyp>Plc44) z$KJI;B#>7CM0uP+h}J4v@HQ~Qi;|2i9s)ds+e)(CGCBluQDoAFo<)2jjuZVn;Ic?@ zM0rsbU`*0?5pIKIh3FN%y(mj1vD=N9{iR&Us1)%(u)nA_VV7eGmQn5*%r;3Iq__0K z7}&y=AO2YlziAdVOTk5wnvQ!03ysN_H7%1LuWcOc?}4iWl>ZE4uCH>;#A6J`_zsTC zGZAxvv0hYM4`v&^kSfLlV0&c;}qPsDx8I#CftNwb5`)z*x3o)3=DC_JOp>HS%2< z`)_bxf=Mdb0h3hcNX$k7*-8uYDR7}kW%Ukiq(vsO&|~s_>;WccFh_YCJT-oSYA=BbE!$92=- zhD%7z;&V4lWMD_pFWQK1(cEY6+JkWnZO5e4TP=FShnUdV?Tv#`BLd<7+??g{*}7TH P00000NkvXXu0mjfsjZoU literal 0 HcmV?d00001 diff --git a/client/webserver/site/src/js/doc.ts b/client/webserver/site/src/js/doc.ts index a934d91eeb..20f38207fe 100644 --- a/client/webserver/site/src/js/doc.ts +++ b/client/webserver/site/src/js/doc.ts @@ -18,7 +18,8 @@ const BipIDs = { 28: 'vtc', 3: 'doge', 145: 'bch', - 60: 'eth' + 60: 'eth', + 133: 'zec' } const BipSymbols = Object.values(BipIDs) diff --git a/dex/networks/zec/addr.go b/dex/networks/zec/addr.go new file mode 100644 index 0000000000..89d4f6dfb7 --- /dev/null +++ b/dex/networks/zec/addr.go @@ -0,0 +1,83 @@ +// This code is available on the terms of the project LICENSE.md file, +// also available online at https://blueoakcouncil.org + +package zec + +import ( + "crypto/sha256" + "fmt" + + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcutil/base58" + dcrchaincfg "github.com/decred/dcrd/chaincfg/v3" +) + +// DecodeAddress decodes an address string into an internal btc address. +// ZCash uses a double SHA-256 checksum but with a 2-byte address ID, so +// a little customization is needed. +// TODO: There also appears to be a bech32 encoding and something called a +// "unified payment address", but for our use of this function client-side, +// we will never generate those addresses. +// Do we need to revisit before NU5? +func DecodeAddress(a string, dcrParams *dcrchaincfg.Params, btcParams *chaincfg.Params) (btcutil.Address, error) { + b := base58.Decode(a) + if len(b) < 7 { + return nil, fmt.Errorf("invalid address") + } + + var addrID [2]byte + copy(addrID[:], b[:2]) + + var checkSum [4]byte + copy(checkSum[:], b[len(b)-4:]) + + data := b[2 : len(b)-4] + hashDigest := b[:len(b)-4] + + if checksum(hashDigest) != checkSum { + return nil, fmt.Errorf("invalid checksum") + } + + switch addrID { + case dcrParams.PubKeyHashAddrID: + return btcutil.NewAddressPubKeyHash(data, btcParams) + case dcrParams.ScriptHashAddrID: + return btcutil.NewAddressScriptHashFromHash(data, btcParams) + } + + return nil, fmt.Errorf("unknown address type %v %v", addrID, dcrParams.PubKeyHashAddrID) +} + +// RecodeAddress converts an internal btc address to a ZCash address string. +func RecodeAddress(addr string, dcrParams *dcrchaincfg.Params, btcParams *chaincfg.Params) (string, error) { + btcAddr, err := btcutil.DecodeAddress(addr, btcParams) + if err != nil { + return "", err + } + + switch btcAddr.(type) { + case *btcutil.AddressPubKeyHash: + return b58Encode(btcAddr.ScriptAddress(), dcrParams.PubKeyHashAddrID), nil + case *btcutil.AddressScriptHash: + return b58Encode(btcAddr.ScriptAddress(), dcrParams.ScriptHashAddrID), nil + } + + return "", fmt.Errorf("unsupported address type %T", btcAddr) +} + +func b58Encode(input []byte, addrID [2]byte) string { + b := make([]byte, 0, 2+len(input)+4) + b = append(b, addrID[:]...) + b = append(b, input[:]...) + cksum := checksum(b) + b = append(b, cksum[:]...) + return base58.Encode(b) +} + +func checksum(input []byte) (cksum [4]byte) { + h := sha256.Sum256(input) + h2 := sha256.Sum256(h[:]) + copy(cksum[:], h2[:4]) + return +} diff --git a/dex/networks/zec/addr_test.go b/dex/networks/zec/addr_test.go new file mode 100644 index 0000000000..39116237be --- /dev/null +++ b/dex/networks/zec/addr_test.go @@ -0,0 +1,30 @@ +package zec + +import ( + "bytes" + "encoding/hex" + "testing" +) + +func TestAddress(t *testing.T) { + pkHash, _ := hex.DecodeString("0ca584c97d2c84ea296524ac89f2febcf1094347") + addr := "t1K2UQ5VzGHGC1ZPqGJXXSocxtjo5s6peSJ" + + btcAddr, err := DecodeAddress(addr, MainNetAddressParams, MainNetParams) + if err != nil { + t.Fatalf("DecodeAddress error: %v", err) + } + + if !bytes.Equal(btcAddr.ScriptAddress(), pkHash) { + t.Fatalf("wrong script address") + } + + reAddr, err := RecodeAddress(btcAddr.String(), MainNetAddressParams, MainNetParams) + if err != nil { + t.Fatalf("RecodeAddress error: %v", err) + } + + if reAddr != addr { + t.Fatalf("wrong recoded address. expected %s, got %s", addr, reAddr) + } +} diff --git a/dex/networks/zec/block.go b/dex/networks/zec/block.go new file mode 100644 index 0000000000..8e341f64d2 --- /dev/null +++ b/dex/networks/zec/block.go @@ -0,0 +1,125 @@ +// This code is available on the terms of the project LICENSE.md file, +// also available online at https://blueoakcouncil.org + +package zec + +import ( + "bytes" + "fmt" + "io" + "time" + + "github.com/btcsuite/btcd/wire" +) + +// ZecBlock extends a wire.MsgBlock to specify ZCash specific fields, or in the +// case of the Nonce, a type-variant. +type ZecBlock struct { + wire.MsgBlock + // Transactions and MsgBlock.Transactions should both be populated. Each + // *ZecTx.MsgTx will be the same as the *MsgTx in MsgBlock.Transactions. + Transactions []*ZecTx + HashBlockCommitments [32]byte // Using NU5 name + Nonce [32]byte // Bitcoin uses uint32 + Solution []byte // length 1344 on main and testnet, 36 on regtest +} + +// DeserializeZecBlock deserializes +func DeserializeZecBlock(b []byte) (*ZecBlock, error) { + zecBlock := &ZecBlock{} + + // https://zips.z.cash/protocol/protocol.pdf section 7.6 + r := bytes.NewReader(b) + + if err := zecBlock.decodeBlockHeader(r); err != nil { + return nil, err + } + + txCount, err := wire.ReadVarInt(r, pver) + if err != nil { + return nil, err + } + + // TODO: Limit txCount based on block size, header size, min tx size. + + zecBlock.MsgBlock.Transactions = make([]*wire.MsgTx, 0, txCount) + zecBlock.Transactions = make([]*ZecTx, 0, txCount) + for i := uint64(0); i < txCount; i++ { + tx := &ZecTx{MsgTx: new(wire.MsgTx)} + + if err := tx.ZecDecode(r); err != nil { + return nil, err + } + zecBlock.MsgBlock.Transactions = append(zecBlock.MsgBlock.Transactions, tx.MsgTx) + zecBlock.Transactions = append(zecBlock.Transactions, tx) + } + + return zecBlock, nil +} + +// See github.com/zcash/zcash CBlockHeader -> SerializeOp +func (z *ZecBlock) decodeBlockHeader(r io.Reader) error { + hdr := &z.MsgBlock.Header + + nVersion, err := readUint32(r) + if err != nil { + return err + } + hdr.Version = int32(nVersion) + + if err = readInternalByteOrder(r, hdr.PrevBlock[:]); err != nil { + return err + } + + if err := readInternalByteOrder(r, hdr.MerkleRoot[:]); err != nil { + return err + } + + _, err = io.ReadFull(r, z.HashBlockCommitments[:]) + if err != nil { + return err + } + + nTime, err := readUint32(r) + if err != nil { + return err + } + hdr.Timestamp = time.Unix(int64(nTime), 0) + + hdr.Bits, err = readUint32(r) + if err != nil { + return err + } + + err = readInternalByteOrder(r, z.Nonce[:]) + if err != nil { + return err + } + + solSize, err := wire.ReadVarInt(r, pver) + if err != nil { + return err + } + if solSize != 1344 && solSize != 36 { + return fmt.Errorf("wrong solution size %d", solSize) + } + z.Solution = make([]byte, solSize) + + _, err = io.ReadFull(r, z.Solution) + if err != nil { + return err + } + + return nil +} + +func readInternalByteOrder(r io.Reader, b []byte) error { + if _, err := io.ReadFull(r, b); err != nil { + return err + } + // Reverse the bytes + for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 { + b[i], b[j] = b[j], b[i] + } + return nil +} diff --git a/dex/networks/zec/block_test.go b/dex/networks/zec/block_test.go new file mode 100644 index 0000000000..05c7b90160 --- /dev/null +++ b/dex/networks/zec/block_test.go @@ -0,0 +1,191 @@ +package zec + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "testing" +) + +func TestBlock(t *testing.T) { + blockB := mustDecodeHex("0400000079a2f1d33fbd51b6fba193893463b44b7257" + + "62f8e18d4d397e713601000000007f7216d545aff45939f7d254d5196ccf991d767f7d" + + "27b00ae77e48d1cfc2ee433d455350e363e7f050abf3c34adb130834e8031b32326483" + + "5a7292974b526ffb433e4e62d0aa011ce2d00000000000000000000000000000000003" + + "0000000000000000004009a4f5fd4005006443aba3157d3ed3842159331256c4ea2d3b" + + "855319937aa7ce72f1f7e14733d356f2217b9a1b1f353e02285e152087c0f8637b8a3d" + + "bdedc72a95425f527027ebae6a5a5534590e34455a0654c3aaab599e948c04a099f840" + + "05ab770a7871bbef7045cdf5b2f7d60c14d999a8d4dadb99449a325dc97e1eedba79d7" + + "3749174789735749511d944003b71ada2d99672cd6ffb5299c0bdb4e0d0c0b11ce5ce0" + + "dd67af0bedf67fdb5f02462e2f038f1b528a30527524305e14fc362cf09e096dd40a18" + + "48489d2fbfd46c4cecaf0b03c3ff3e20053be99ed0e7e0937ddd81a958362ad0fd9f54" + + "55dc1b941fb6dedf073b4b23e249c245d3e5d409fe813e042e09f56e0751c8a64d3225" + + "cc3aeb56737a93cb9c1999d2bc44ada8e991d4193f125b575a722f3f557616e21d88ce" + + "6436f7eaaea4dfecad98893b2cf4e5e126c956a8f80ba8cb33596379a8e76e5126c08b" + + "38c5013bc5c9cdd1becbac6a90abc2a3bab9b5d854534507f32ef225261d313a2c70f8" + + "f421a1606bf2d978c808e8b7422bd935a0cc9e130174dfecd4fc526b2d5561cb7fe009" + + "28efc1b91bc702b44ec4ddf7d311a3140b4a5cb24a97f0a8da7b0879ab588d422dcfbe" + + "53a6407daec0a2da4b1f45f465886247375d92280ffc6821dd933e3490766ab63f031a" + + "187ecbd96ab0cded393c50e644ec157504f61763d3d6aed65ee6bfbd1e1f046120d2eb" + + "1143b8db4a9259f14071c111abaee41f2d6bfa315fa9b9fb946974641d5815ba9fc935" + + "8db908fa0fba4f44145a80eaa227c73809bace4a37ea76426ad6235a567c3f403ff445" + + "05316e1df97171f8f20a1421c599a5a407996343fd8be00337ab6b3e89ea3a5afe8d6b" + + "1dbded5a37066ea541803f8fccbe63731f72c9c19f08070078b05428735d1ffabb595f" + + "72a836bfe1c6c251b814defb7380b267884d543e1a780700f3f57c724e3e18f2e3090d" + + "f4f452b65ab2b636ff172f1d2fefd06e45a8e91177d857bc34fe5b9cb3453a00bbdab3" + + "efc9e5cbb1d53abe55180d7ac95a221c49c197503618b442f574a625b071c9aa5a95df" + + "91b21fae05bba68fc9b5cab924568f36e031de2c77b30a31d6961de61a415b32fc44de" + + "31de5622859f5a1d1f2781097454cd0424c1cd63fca937b73e4e15f6cca829920b4d57" + + "a6a4416f7e743d583e3b677dfebedde506289b58400d8730a85654c53652810a36ea73" + + "f1110eae15f8304534b7bb5fe163ed94ec495a514f6ad34cc98f3472e5f3f5e81af7e7" + + "90f7ae5a91cbbc7a2d5ae6f38c85e08367873cd622534b1ebb690adfaf470bc28ad9cf" + + "038f476506530bd1a80be599b05b8a7e5486d6443da4402daa0e372b7c501f8a4fdef3" + + "25082e7936ac6714426fb334c5c9c24bc071eae5f907e34701b424a12cef39b686e50c" + + "0ad86baa5ad6b98a34013e13dfe8df895dbeb5d2235bf48965055919350e0bbb6fd2cf" + + "4ad72b8a3e712f8a4bd7516f652d0b2702d652a7a2eb7b0ddaabe0b06d44ff49f186b2" + + "afa911a0b98b108558d839a0019d529ddb0574644ea7ee0ce16944dc1033e3638402eb" + + "64712dd6467a92aee70d97f930a3500fd39e67f19fee7caecbf4047fda681942f1724d" + + "f387c9c63426aebc6cde8a92375cec6d5cbd0cf68332be45ac3da7181ec77b96895937" + + "c757023f70457acef3869330827a1f6511d15fb5176b1d0d81a75c03d2fed0bc1b713c" + + "c8f12f95c56c5aad69076e0ab81876cb47dcf143011ad20009ddfd9390c8215f019a06" + + "9d3ac93ace45d86fca9a198913d8720805e4d77b0782a7e355ae4067856e0b7d7e462f" + + "7ba21517fc89e5561adb2129bbc8737d786f0f90df606a154955b845889283b74704ce" + + "c758fbce1bc9713c2f170569962567e27dcdf7f5f4bdee1ac2c3d877988e0104000080" + + "85202f8901000000000000000000000000000000000000000000000000000000000000" + + "0000ffffffff210387c9181c4d696e656420627920416e74506f6f6c36323361001f03" + + "20a3c16b65ffffffff0438c94d010000000017a914df37da178caa3ba0cc7feea71cd8" + + "63c6677f4ea587286bee000000000017a914d45cb1adffb5215a42720532a076f02c7c" + + "778c908740787d010000000017a914931fec54c1fea86e574462cc32013f5400b89129" + + "8780b2e60e000000001976a9145c38e5e20b62bbb5683dd68677ac3715047341ea88ac" + + "00000000000000000000000000000000000000") + + // expHash := mustDecodeHex("00000000015c8a406ff880c5be4d2ae2744eab8be02a33d0179d68f47e51ea82") + // const expHeight = 1624455 + + const expVersion = 4 + expPrevBlock := mustDecodeHex("000000000136717e394d8de1f86257724bb463348993a1fbb651bd3fd3f1a279") + expMerkleRoot := mustDecodeHex("43eec2cfd1487ee70ab0277d7f761d99cf6c19d554d2f73959f4af45d516727f") + // expHashBlockCommitments := mustDecodeHex("30702d1b320e2ea8f603aa8fb54baea5581761d19fccf5061ac82e81d8fdeea4") + expNonce := mustDecodeHex("f5a409400000000000000000000300000000000000000000000000000000d0e2") + expSolution := mustDecodeHex("006443aba3157d3ed3842159331256c4ea2d3b855" + + "319937aa7ce72f1f7e14733d356f2217b9a1b1f353e02285e152087c0f8637b8a3" + + "dbdedc72a95425f527027ebae6a5a5534590e34455a0654c3aaab599e948c04a09" + + "9f84005ab770a7871bbef7045cdf5b2f7d60c14d999a8d4dadb99449a325dc97e1" + + "eedba79d73749174789735749511d944003b71ada2d99672cd6ffb5299c0bdb4e0" + + "d0c0b11ce5ce0dd67af0bedf67fdb5f02462e2f038f1b528a30527524305e14fc3" + + "62cf09e096dd40a1848489d2fbfd46c4cecaf0b03c3ff3e20053be99ed0e7e0937" + + "ddd81a958362ad0fd9f5455dc1b941fb6dedf073b4b23e249c245d3e5d409fe813" + + "e042e09f56e0751c8a64d3225cc3aeb56737a93cb9c1999d2bc44ada8e991d4193" + + "f125b575a722f3f557616e21d88ce6436f7eaaea4dfecad98893b2cf4e5e126c95" + + "6a8f80ba8cb33596379a8e76e5126c08b38c5013bc5c9cdd1becbac6a90abc2a3b" + + "ab9b5d854534507f32ef225261d313a2c70f8f421a1606bf2d978c808e8b7422bd" + + "935a0cc9e130174dfecd4fc526b2d5561cb7fe00928efc1b91bc702b44ec4ddf7d" + + "311a3140b4a5cb24a97f0a8da7b0879ab588d422dcfbe53a6407daec0a2da4b1f4" + + "5f465886247375d92280ffc6821dd933e3490766ab63f031a187ecbd96ab0cded3" + + "93c50e644ec157504f61763d3d6aed65ee6bfbd1e1f046120d2eb1143b8db4a925" + + "9f14071c111abaee41f2d6bfa315fa9b9fb946974641d5815ba9fc9358db908fa0" + + "fba4f44145a80eaa227c73809bace4a37ea76426ad6235a567c3f403ff44505316" + + "e1df97171f8f20a1421c599a5a407996343fd8be00337ab6b3e89ea3a5afe8d6b1" + + "dbded5a37066ea541803f8fccbe63731f72c9c19f08070078b05428735d1ffabb5" + + "95f72a836bfe1c6c251b814defb7380b267884d543e1a780700f3f57c724e3e18f" + + "2e3090df4f452b65ab2b636ff172f1d2fefd06e45a8e91177d857bc34fe5b9cb34" + + "53a00bbdab3efc9e5cbb1d53abe55180d7ac95a221c49c197503618b442f574a62" + + "5b071c9aa5a95df91b21fae05bba68fc9b5cab924568f36e031de2c77b30a31d69" + + "61de61a415b32fc44de31de5622859f5a1d1f2781097454cd0424c1cd63fca937b" + + "73e4e15f6cca829920b4d57a6a4416f7e743d583e3b677dfebedde506289b58400" + + "d8730a85654c53652810a36ea73f1110eae15f8304534b7bb5fe163ed94ec495a5" + + "14f6ad34cc98f3472e5f3f5e81af7e790f7ae5a91cbbc7a2d5ae6f38c85e083678" + + "73cd622534b1ebb690adfaf470bc28ad9cf038f476506530bd1a80be599b05b8a7" + + "e5486d6443da4402daa0e372b7c501f8a4fdef325082e7936ac6714426fb334c5c" + + "9c24bc071eae5f907e34701b424a12cef39b686e50c0ad86baa5ad6b98a34013e1" + + "3dfe8df895dbeb5d2235bf48965055919350e0bbb6fd2cf4ad72b8a3e712f8a4bd" + + "7516f652d0b2702d652a7a2eb7b0ddaabe0b06d44ff49f186b2afa911a0b98b108" + + "558d839a0019d529ddb0574644ea7ee0ce16944dc1033e3638402eb64712dd6467" + + "a92aee70d97f930a3500fd39e67f19fee7caecbf4047fda681942f1724df387c9c" + + "63426aebc6cde8a92375cec6d5cbd0cf68332be45ac3da7181ec77b96895937c75" + + "7023f70457acef3869330827a1f6511d15fb5176b1d0d81a75c03d2fed0bc1b713" + + "cc8f12f95c56c5aad69076e0ab81876cb47dcf143011ad20009ddfd9390c8215f0" + + "19a069d3ac93ace45d86fca9a198913d8720805e4d77b0782a7e355ae4067856e0" + + "b7d7e462f7ba21517fc89e5561adb2129bbc8737d786f0f90df606a154955b8458" + + "89283b74704cec758fbce1bc9713c2f170569962567e27dcdf7f5f4bdee1ac2c3d" + + "877988e") + + expBits := binary.LittleEndian.Uint32(mustDecodeHex("d0aa011c")) + const expTime = 1649294915 + + zecBlock, err := DeserializeZecBlock(blockB) + if err != nil { + t.Fatalf("decodeBlockHeader error: %v", err) + } + + hdr := &zecBlock.MsgBlock.Header + + if hdr.Version != expVersion { + t.Fatalf("wrong version. expected %d, got %d", expVersion, hdr.Version) + } + + if !bytes.Equal(expPrevBlock, hdr.PrevBlock[:]) { + t.Fatal("wrong previous block", expPrevBlock, hdr.PrevBlock[:]) + } + + if !bytes.Equal(expMerkleRoot, hdr.MerkleRoot[:]) { + t.Fatal("wrong merkle root", expMerkleRoot, hdr.MerkleRoot[:]) + } + + // TODO: Find out why this is not right. + // if !bytes.Equal(zecBlock.HashBlockCommitments[:], expHashBlockCommitments) { + // t.Fatal("wrong hashBlockCommitments", zecBlock.HashBlockCommitments[:], expHashBlockCommitments, h) + // } + + if hdr.Bits != expBits { + t.Fatalf("wrong bits") + } + + if hdr.Timestamp.Unix() != expTime { + t.Fatalf("wrong timestamp") + } + + if !bytes.Equal(zecBlock.Nonce[:], expNonce) { + t.Fatal("wrong nonce", zecBlock.Nonce[:], expNonce) + } + + if !bytes.Equal(zecBlock.Solution[:], expSolution) { + t.Fatal("wrong solution") + } + + if len(zecBlock.Transactions) != 1 { + t.Fatalf("expected 1 transaction, got %d", len(zecBlock.Transactions)) + } + + tx := zecBlock.Transactions[0] + + if len(tx.TxIn) != 1 { + t.Fatalf("wrong number of tx inputs. expected 1, got %d", len(tx.TxIn)) + } + + if len(tx.TxOut) != 4 { + t.Fatalf("wrong number of tx outputs. expected 4, got %d", len(tx.TxOut)) + } +} + +func TestSimnetBlockHeader(t *testing.T) { + blockB := mustDecodeHex("04000000b001ef4cf473a983d7d0eccf23195979d92dc4b420" + + "fcbdc6a13213486598b00c6e046134fc6cd860745eb62e0f154a86c2f5a1a017b55d23" + + "685042db9e6faf6d81bc5c1e65dcd92f388d8de8ffd57369885c1997046566f3e633ad" + + "5b795f2005be3d4e620f0f0f2004008fa513bd505a57c5a0831dbc01943b12d74e2f0a" + + "9e766fe5767436a900002400e3ce180372a971362443d91ef2df1dbfdd15314c9232a3" + + "39b1de3ca4501da5bbc371fe") + + zecBlock := &ZecBlock{} + if err := zecBlock.decodeBlockHeader(bytes.NewReader(blockB)); err != nil { + t.Fatalf("decodeBlockHeader error: %v", err) + } +} + +func mustDecodeHex(hx string) []byte { + b, err := hex.DecodeString(hx) + if err != nil { + panic("mustDecodeHex: " + err.Error()) + } + return b +} diff --git a/dex/networks/zec/params.go b/dex/networks/zec/params.go new file mode 100644 index 0000000000..006ea4f41b --- /dev/null +++ b/dex/networks/zec/params.go @@ -0,0 +1,86 @@ +// This code is available on the terms of the project LICENSE.md file, +// also available online at https://blueoakcouncil.org + +package zec + +import ( + "decred.org/dcrdex/dex" + "decred.org/dcrdex/dex/networks/btc" + "github.com/btcsuite/btcd/chaincfg" + dcrchaincfg "github.com/decred/dcrd/chaincfg/v3" +) + +const ( + // MinimumTxOverhead + // 4 header + 4 nVersionGroup + 1 varint input count + 1 varint output count + // + 4 lockTime + 4 nExpiryHeight + 8 valueBalanceSapling + 1 varint nSpendsSapling + // + 1 varint nOutputsSapling + 1 varint nJoinSplit + MinimumTxOverhead = 29 + + InitTxSizeBase = MinimumTxOverhead + btc.P2PKHOutputSize + btc.P2SHOutputSize // 29 + 34 + 32 = 95 + InitTxSize = InitTxSizeBase + btc.RedeemP2PKHInputSize // 95 + 149 = 244 +) + +var ( + UnitInfo = dex.UnitInfo{ + AtomicUnit: "Sats", + Conventional: dex.Denomination{ + Unit: "ZEC", + ConversionFactor: 1e8, + }, + } + + // MainNetParams are the clone parameters for mainnet. ZCash, + // like Decred, uses two bytes for their address IDs. We will convert + // between address types on the fly and use these spoof parameters + // internally. + MainNetParams = btc.ReadCloneParams(&btc.CloneParams{ + ScriptHashAddrID: 0xBD, + PubKeyHashAddrID: 0xB8, + CoinbaseMaturity: 100, + Net: 0x24e92764, + }) + // TestNet4Params are the clone parameters for testnet. + TestNet4Params = btc.ReadCloneParams(&btc.CloneParams{ + PubKeyHashAddrID: 0x25, + ScriptHashAddrID: 0xBA, + CoinbaseMaturity: 100, + Net: 0xfa1af9bf, + }) + // RegressionNetParams are the clone parameters for simnet. + RegressionNetParams = btc.ReadCloneParams(&btc.CloneParams{ + PubKeyHashAddrID: 0x25, + ScriptHashAddrID: 0xBA, + CoinbaseMaturity: 100, + Net: 0xaae83f5f, + }) + + // MainNetAddressParams are used for string address parsing. We use a + // spoofed address internally, since ZCash uses a two-byte address ID + // instead of a 1-byte ID. + MainNetAddressParams = &dcrchaincfg.Params{ + ScriptHashAddrID: [2]byte{0x1C, 0xBD}, + PubKeyHashAddrID: [2]byte{0x1C, 0xB8}, + } + + // TestNet4AddressParams are used for string address parsing. + TestNet4AddressParams = &dcrchaincfg.Params{ + ScriptHashAddrID: [2]byte{0x1C, 0xBA}, + PubKeyHashAddrID: [2]byte{0x1D, 0x25}, + } + + // RegressionNetAddressParams are used for string address parsing. + RegressionNetAddressParams = &dcrchaincfg.Params{ + ScriptHashAddrID: [2]byte{0x1C, 0xBA}, + PubKeyHashAddrID: [2]byte{0x1D, 0x25}, + } +) + +func init() { + for _, params := range []*chaincfg.Params{MainNetParams, TestNet4Params, RegressionNetParams} { + err := chaincfg.Register(params) + if err != nil { + panic("failed to register zec parameters: " + err.Error()) + } + } +} diff --git a/dex/networks/zec/tx.go b/dex/networks/zec/tx.go new file mode 100644 index 0000000000..8166338829 --- /dev/null +++ b/dex/networks/zec/tx.go @@ -0,0 +1,417 @@ +// This code is available on the terms of the project LICENSE.md file, +// also available online at https://blueoakcouncil.org + +package zec + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" +) + +const ( + VersionSapling int32 = 4 + VersionCanopy int32 = 5 + MaxExpiryHeight = 499999999 // https://zips.z.cash/zip-0203 + + versionSaplingGroupID = 0x892f2085 + + overwinterMask = ^uint32(1 << 31) + pver = 0 +) + +// ZecTx +type ZecTx struct { + *wire.MsgTx + ExpiryHeight uint32 +} + +func NewZecTxFromMsgTx(tx *wire.MsgTx, expiryHeight uint32, zecVersion int32) *ZecTx { + zecTx := &ZecTx{ + MsgTx: tx, + ExpiryHeight: expiryHeight, + } + zecTx.Version = zecVersion + return zecTx +} + +// TxHash generates the Hash for the transaction. +func (tx *ZecTx) TxHash() chainhash.Hash { + b, _ := tx.Bytes() + return chainhash.DoubleHashH(b) +} + +// ZecEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +// See Serialize for encoding transactions to be stored to disk, such as in a +// database, as opposed to encoding transactions for the wire. +// msg.Version must be 4. +func (tx *ZecTx) Bytes() ([]byte, error) { + w := new(bytes.Buffer) + if tx.Version != 4 { + return nil, fmt.Errorf("only version 4 (sapling) supported") + } + + err := putUint32(w, uint32(tx.Version)|(1<<31)) + if err != nil { + return nil, err + } + + err = putUint32(w, versionSaplingGroupID) + if err != nil { + return nil, err + } + + count := uint64(len(tx.MsgTx.TxIn)) + err = wire.WriteVarInt(w, pver, count) + if err != nil { + return nil, err + } + + for _, ti := range tx.TxIn { + err = writeTxIn(w, tx.Version, ti) + if err != nil { + return nil, err + } + } + + count = uint64(len(tx.TxOut)) + err = wire.WriteVarInt(w, pver, count) + if err != nil { + return nil, err + } + + for _, to := range tx.TxOut { + err = wire.WriteTxOut(w, pver, tx.Version, to) + if err != nil { + return nil, err + } + } + + if err = putUint32(w, tx.LockTime); err != nil { + return nil, err + } + + if err = putUint32(w, tx.ExpiryHeight); err != nil { + return nil, err + } + + if tx.Version == VersionSapling { + // valueBalance + if err := putUint64(w, 0); err != nil { + return nil, err + } + + // nShieldedSpend + err = wire.WriteVarInt(w, pver, 0) + if err != nil { + return nil, err + } + + // nShieldedOutput + err = wire.WriteVarInt(w, pver, 0) + if err != nil { + return nil, err + } + } + + if err := wire.WriteVarInt(w, pver, 0); err != nil { + return nil, err + } + + return w.Bytes(), nil +} + +// see https://zips.z.cash/protocol/protocol.pdf section 7.1 +func DeserializeZecTx(b []byte) (*ZecTx, error) { + tx := &ZecTx{MsgTx: new(wire.MsgTx)} + r := bytes.NewReader(b) + if err := tx.ZecDecode(r); err != nil { + return nil, err + } + return tx, nil +} + +// ZecDecode reads the serialized transaction from the reader and populates the +// *ZecTx's fields. +func (tx *ZecTx) ZecDecode(r io.Reader) error { + ver, err := readUint32(r) + if err != nil { + return err + } + + // overWintered := (ver & (1 << 31)) > 0 + ver &= overwinterMask // Clear the overwinter bit + tx.Version = int32(ver) + + if ver > 4 { + return fmt.Errorf("unsupported tx version %d > 4", ver) + } + + if ver > 3 { + _, err := readUint32(r) + if err != nil { + return err + } + } + + txInCount, err := wire.ReadVarInt(r, pver) + if err != nil { + return err + } + + tx.TxIn = make([]*wire.TxIn, 0, txInCount) + for i := 0; i < int(txInCount); i++ { + ti := new(wire.TxIn) + if err := readTxIn(r, pver, int32(ver), ti); err != nil { + return err + } + tx.TxIn = append(tx.TxIn, ti) + } + + txOutCount, err := wire.ReadVarInt(r, pver) + if err != nil { + return err + } + + tx.TxOut = make([]*wire.TxOut, 0, txOutCount) + for i := 0; i < int(txOutCount); i++ { + to := new(wire.TxOut) + if err := readTxOut(r, pver, int32(ver), to); err != nil { + return err + } + tx.TxOut = append(tx.TxOut, to) + } + + tx.LockTime, err = readUint32(r) + if err != nil { + return err + } + + if ver > 3 { + tx.ExpiryHeight, err = readUint32(r) + if err != nil { + return err + } + } + + // Empty the rest of the buffer. + if ver < 2 { + return nil + } + + var bindingSigRequired bool + if ver >= 4 { + // valueBalanceSpending + if _, err := readUint64(r); err != nil { + return fmt.Errorf("error reading valueBalanceSpending: %w", err) + } + + if nSpendsSapling, err := wire.ReadVarInt(r, pver); err != nil { + return fmt.Errorf("error reading nSpendsSapling: %w", err) + } else if nSpendsSapling > 0 { + // vSpendsSapling - discard + bindingSigRequired = true + if _, err := io.ReadFull(r, make([]byte, nSpendsSapling*384)); err != nil { + return fmt.Errorf("error reading vSpendsSapling: %w", err) + } + } + + if nOutputsSapling, err := wire.ReadVarInt(r, pver); err != nil { + return fmt.Errorf("error reading nOutputsSapling: %w", err) + } else if nOutputsSapling > 0 { + // vOutputsSapling - discard + bindingSigRequired = true + if _, err := io.ReadFull(r, make([]byte, nOutputsSapling*948)); err != nil { + return fmt.Errorf("error reading vOutputsSapling: %w", err) + } + } + + } + + if nJoinSplit, err := wire.ReadVarInt(r, pver); err != nil { + return fmt.Errorf("error reading nJoinSplit: %w", err) + } else if nJoinSplit > 0 { + // vJoinSplit - discard + sz := 1802 * nJoinSplit + if ver == 4 { + sz = 1698 * nJoinSplit + } + if _, err := io.ReadFull(r, make([]byte, sz)); err != nil { + return fmt.Errorf("error reading vJoinSplit: %w", err) + } + var joinSplitPubKey [32]byte + if _, err := io.ReadFull(r, joinSplitPubKey[:]); err != nil { + return fmt.Errorf("error reading joinSplitPubKey: %w", err) + } + + var joinSplitSig [64]byte + if _, err := io.ReadFull(r, joinSplitSig[:]); err != nil { + return fmt.Errorf("error reading joinSplitSig: %w", err) + } + } + + if ver == 4 && bindingSigRequired { + var bindingSigSapling [64]byte + if _, err := io.ReadFull(r, bindingSigSapling[:]); err != nil { + return fmt.Errorf("error reading bindingSigSapling: %w", err) + } + } + + return nil +} + +// writeTxIn encodes ti to the bitcoin protocol encoding for a transaction +// input (TxIn) to w. +func writeTxIn(w io.Writer, version int32, ti *wire.TxIn) error { + err := writeOutPoint(w, version, &ti.PreviousOutPoint) + if err != nil { + return err + } + + err = wire.WriteVarBytes(w, pver, ti.SignatureScript) + if err != nil { + return err + } + + return putUint32(w, ti.Sequence) +} + +// writeOutPoint encodes op to the bitcoin protocol encoding for an OutPoint +// to w. +func writeOutPoint(w io.Writer, version int32, op *wire.OutPoint) error { + _, err := w.Write(op.Hash[:]) + if err != nil { + return err + } + return putUint32(w, op.Index) +} + +// putUint32 writes a little-endian encoded uint32 to the Writer. +func putUint32(w io.Writer, v uint32) error { + b := make([]byte, 4) + binary.LittleEndian.PutUint32(b, v) + _, err := w.Write(b) + return err +} + +// putUint32 writes a little-endian encoded uint64 to the Writer. +func putUint64(w io.Writer, v uint64) error { + b := make([]byte, 8) + binary.LittleEndian.PutUint64(b, v) + _, err := w.Write(b) + return err +} + +// readUint32 reads a little-endian encoded uint32 from the Reader. +func readUint32(r io.Reader) (uint32, error) { + b := make([]byte, 4) + if _, err := io.ReadFull(r, b); err != nil { + return 0, err + } + return binary.LittleEndian.Uint32(b), nil +} + +// readUint64 reads a little-endian encoded uint64 from the Reader. +func readUint64(r io.Reader) (uint64, error) { + b := make([]byte, 8) + if _, err := io.ReadFull(r, b); err != nil { + return 0, err + } + return binary.LittleEndian.Uint64(b), nil +} + +// readTxIn reads the next sequence of bytes from r as a transaction input. +func readTxIn(r io.Reader, pver uint32, version int32, ti *wire.TxIn) error { + err := readOutPoint(r, pver, version, &ti.PreviousOutPoint) + if err != nil { + return err + } + + ti.SignatureScript, err = readScript(r, pver) + if err != nil { + return err + } + + ti.Sequence, err = readUint32(r) + return err +} + +// readTxOut reads the next sequence of bytes from r as a transaction output. +func readTxOut(r io.Reader, pver uint32, version int32, to *wire.TxOut) error { + v, err := readUint64(r) + if err != nil { + return err + } + to.Value = int64(v) + + to.PkScript, err = readScript(r, pver) + return err +} + +// readOutPoint reads the next sequence of bytes from r as an OutPoint. +func readOutPoint(r io.Reader, pver uint32, version int32, op *wire.OutPoint) error { + _, err := io.ReadFull(r, op.Hash[:]) + if err != nil { + return err + } + + op.Index, err = readUint32(r) + return err +} + +// readScript reads a variable length byte array. Copy of unexported +// btcd/wire.readScript. +func readScript(r io.Reader, pver uint32) ([]byte, error) { + count, err := wire.ReadVarInt(r, pver) + if err != nil { + return nil, err + } + if count > uint64(wire.MaxMessagePayload) { + return nil, fmt.Errorf("larger than the max allowed size "+ + "[count %d, max %d]", count, wire.MaxMessagePayload) + } + b := make([]byte, count) + _, err = io.ReadFull(r, b) + if err != nil { + return nil, err + } + return b, nil +} + +// CalcTxSize calculates the size of a ZCash transparent transaction. CalcTxSize +// won't return accurate results for shielded or blended transactions. +func CalcTxSize(tx *wire.MsgTx) uint64 { + var sz uint64 = 4 // header + ver := tx.Version + if ver > 3 { + sz += 4 // nVersionGroup + } + sz += uint64(wire.VarIntSerializeSize(uint64(len(tx.TxIn)))) + for _, txIn := range tx.TxIn { + sz += 32 /* prev hash */ + 4 /* prev index */ + 4 /* sequence */ + sz += uint64(wire.VarIntSerializeSize(uint64(len(txIn.SignatureScript)))) + uint64(len(txIn.SignatureScript)) + } + sz += uint64(wire.VarIntSerializeSize(uint64(len(tx.TxOut)))) + for _, txOut := range tx.TxOut { + sz += 8 /* Value */ + sz += uint64(wire.VarIntSerializeSize(uint64(len(txOut.PkScript)))) + uint64(len(txOut.PkScript)) + } + sz += 4 // lockTime + if ver >= 3 { + sz += 4 // nExpiryHeight + } + if ver >= 4 { + sz += 8 // valueBalanceSapling + sz += 1 // nSpendsSapling varint + sz += 1 // nOutputsSapling varint + } + if ver > 2 { + sz += 1 // nJoinSplit varint = 0 + } + return sz +} diff --git a/dex/networks/zec/tx_test.go b/dex/networks/zec/tx_test.go new file mode 100644 index 0000000000..30b186db83 --- /dev/null +++ b/dex/networks/zec/tx_test.go @@ -0,0 +1,143 @@ +package zec + +import ( + "bytes" + "encoding/hex" + "testing" +) + +func TestTxDeserialize(t *testing.T) { + // mainnet tx fb70397806afddcc07b9607e844ff29f2fb09e9972a051c3fe4d56fe18147e77 + txB, _ := hex.DecodeString("0400008085202f8901690d272a3549a3f2b57148019a942412" + + "6a94e8791762bd4154331d00ac4705df000000006a47304402201656a4834651f39ac5" + + "2eb866042ca7ede052ac843a914da4790573122c8e2ab302200af617e856abf4f8fb8d" + + "8086825dc63766943b4866ad3d6b4c8f222017c9b402012102d547eb1c5672a4d212de" + + "3c797a87b2b8fe731c2b502db6d7ad044850fe11d78fffffffff02a0fe7d0300000000" + + "1976a91462b9991cb8310a8a74bd6359e954fb812fa7d37b88acced877020000000019" + + "76a9144ff496917bae33309a8ad70bec81355bbf92988988ac00000000000000000000" + + "000000000000000000") + + tx, err := DeserializeZecTx(txB) + if err != nil { + t.Fatalf("error decoding tx: %v", err) + } + + if len(tx.TxIn) != 1 { + t.Fatalf("expected 1 input, got %d", len(tx.TxIn)) + } + + txIn := tx.TxIn[0] + if txIn.PreviousOutPoint.Hash.String() != "df0547ac001d335441bd621779e8946a1224949a014871b5f2a349352a270d69" { + t.Fatal("wrong previous outpoint hash") + } + if txIn.PreviousOutPoint.Index != 0 { + t.Fatal("wrong previous outpoint index") + } + if hex.EncodeToString(txIn.SignatureScript) != "47304402201656a4834651f39ac52eb866042ca7ede052ac843a914da4790573122c8e2ab302200af617e856abf4f8fb8d8086825dc63766943b4866ad3d6b4c8f222017c9b402012102d547eb1c5672a4d212de3c797a87b2b8fe731c2b502db6d7ad044850fe11d78f" { + t.Fatal("wrong signature script") + } + if txIn.Sequence != 4294967295 { + t.Fatalf("wrong sequence") + } + + if len(tx.TxOut) != 2 { + t.Fatalf("expected 2 outputs, got %d", len(tx.TxOut)) + } + txOut := tx.TxOut[1] + if txOut.Value != 41408718 { + t.Fatal("wrong value") + } + if hex.EncodeToString(txOut.PkScript) != "76a9144ff496917bae33309a8ad70bec81355bbf92988988ac" { + t.Fatalf("wrong pk script") + } + + if sz := CalcTxSize(tx.MsgTx); sz != uint64(len(txB)) { + t.Fatalf("wrong calculated tx size. wanted %d, got %d", len(txB), sz) + } + + serializedTx, err := tx.Bytes() + if err != nil { + t.Fatalf("error re-serializing: %v", err) + } + if !bytes.Equal(serializedTx, txB) { + t.Fatalf("re-encoding does not match original") + } +} + +func TestShieldedTx(t *testing.T) { + txB, _ := hex.DecodeString("0400008085202f89000000000000e6ba1800e8030000000" + + "000000164873d5fc82f2480cba8b884e0854eae7f5b688740f7e9747741edb459732ee" + + "151088c01837a0d0bee69c35b058af0f317b641b9b978f988242e8d9ddbf29b524964d" + + "d00cef61f4f841960c7e8ef7fc00bc2762a05acc375920b0301908c81d73489be2ed11" + + "54f9b9e3e1aa98a97cefaec95458f4c04876c54219db5e39b06dbb47a4cf7d33c5cd57" + + "98ded111230d70c1f02ad118f54aabd6ff5f90404a0516dd09db9f8ed2107b21d975f2" + + "ddd9b40b0a82f323df447b247b99d347167e7fc4a0d3d82664cb8ecf6b56c83891ce50" + + "6c85206ea3d3715823ccc388a799aed215616d7cac52abc254a5773bb272685392a64b" + + "6bbcbda279dd3dd0c4a58f6658d6b07b08f33a57c3311a4bfc945f1dc0798a0a42a565" + + "1bdafa643ae168af3bc3ff0786e8f6fe6760f33eb4ccfea8e42680344c2ef2f995360b" + + "ed128714440248c095fb8ad678e6c5d2bd55f540c0d3cd0e7f5b85912bf53b22d60fe6" + + "8b050d7dee38267461edf0c3e26a0c109df3c66dc7f926832da044e81d6ed1079b22f4" + + "ed50102917b35881933eb2d62e6f40f5f7ce5fa5c1696b2f9cb673b1391ba52b37c651" + + "7a99dac81338796fff6f148cc0687ab239053c2deeb9714d6f367ffd0859b0733236cd" + + "cb28485f8e4397ec9cbb51fad781a239d4489f42b22fb3210e485afb4020ddd2e04d8b" + + "38b17f56fbbf14f1071f8bbec9eecf1b0fb305f7cd157129057a638e4226ef189bc30f" + + "151e0445f865aa0fa7b1e0c85d76ee319b33e86257366982556498f8629d9a7c5a3cda" + + "b3b5657514c5726ce22177e5087664ebf328d38d80e44bbf009b8db43ced4e025d54da" + + "930aa5f28a5ca1f8b6e903cc03377ce94b2dbec72602448ecf8418eaf7ceff76e15d4d" + + "12289f06a0df37598b587960448a1cf38ec7969b9a42beb8ddc35f200dd5beace2e0ab" + + "283508dbed45a5e394fe8571cee5d828bcb16e01870ff770254f64d71809292e80e813" + + "d8e0c683df3da69a8b42bc10b6bcbdbd8b705b0d5be474ef58f54497b9c943694a517f" + + "851c6b7d6c2da569fe2a5d7d2b4da9a4b99b635299d33c0357616b0d67d06a67d88158" + + "51a058c875520d3090e7b51d562ba7648e6ba7e21a28492e79b65b009f79b973f7995a" + + "c053fe490b252da3f5e51f1b6316256e03989d12b3a69dd0a9e451290d2bdf00054a12" + + "0813cc0d74c07793b2000a4c8a4243952d540bf971673f01b2de9a7679507043d73e89" + + "b27759c898f3a40bfcc0ccd1d56352334d3622e3ec0e2036b6ac5d3238b667e2629838" + + "348013d58506995c24e209e67a1d5cbe99bd8764bd571744bf206731c3e35b1afc8542" + + "aec0dc85a69e1985d9797a2e218dddd39df094ee4995a267b7fb65340356904693cd69" + + "d23bee1e3e12c03e9a59262eaa016581b530deb72b6f313a040ba4d90153a1509e7b24" + + "df7c317e898f90044578feab15117c04b30e9d06bf0de9319387702a0d4f21a296315f" + + "e6c2167e88749c991b2b51fec5207e5d0edf8990d493a793a0bcf373c03f8b6b436ff9" + + "b48832f4833c28270640ea25800e6dc41980820b02076de0990de1891717a0f1f4a85a" + + "b7f54134151fb55e1d302348d026edc30f524627c04ae1099b7b3d8c7ea0fc257001a5" + + "0d95afc93b2df78a3aeda58f65f2e820a3b53d4f03fc4977cda9115dc7e24b2301450b" + + "664e69235eaff857eb1b5206f22e5f18b4fa20b9b0707169223b4e37fe7f19ebca2f5b" + + "6baea625d98cff9a7e6e9d2eba255800af0e4674bd0aea7eedef463bd74e16379930e0" + + "e328b20e8c7b3056b21be67dd740de7b68549674cec867076b94b8265b31a452b5d942" + + "34b3c5ccc3292e5acd6ff0c087eddbe1978909748bc56aaf98ad6494bd5132b38f1894" + + "ac1d2a274ccdfc7bfbde908def501940819050196c451d656e1fad85d7eca7a68a901d" + + "3726331ee5fdfec0db09e1213cd5ef3334f22d465c9329bf1794f2e4c1d21e47df8ca0" + + "2ffe71a33979a0ea1df046240b661931eddeb3a61323c5af09bf8e58d243f2d2362ce2" + + "6892ccb58743c3399ae203aba7585a8e4e31bfb834d1d6d2e3f43c5eb46aaddddf214c" + + "3ade0a5174881868f53597e63ce63f0f06e13ce92935a8fe0c953b62bcd68a5f8d26c7" + + "7979ae1002d8e8b95984cadd8f0c152fa2de6133b28b2176bab50542b2736549e815b5" + + "354b1adb5a1859ef32ae81d70ed015a012674f7c8054802a631e12793620ca9d2e0475" + + "a7b3abcaa76cd12c5da569e4b04f9f7aea91e244ea3f65cbb09805ee77c991d1f51c80" + + "8ec22160e8aaab6aada6cc7319adc3efc69330336dd53d935ae6987bad0398820270f5" + + "34abb4c5149acc1b8f391ab1e001560d7ff7d0e84f92fba31f274a18ccae54aac97bec" + + "7f99c8114301f8f2abaa4f8ca89afab825bf44803259c21d18840ef76d169f25512a66" + + "4e8d2081664cb941dc7198243248d2f11372f2045c828d516b896865fe9c3f15be24f3" + + "9a7e4289580cd101a1ee60eb53cba37797ec54391e8531c601c183cc45a94098e5e372" + + "8888cc9488365da3026bdf7a4203c5eadd93146ea848d9606c8fae22e041700ac95da0" + + "2b64c89f7219003cecb2b74e7e001fe7d83c3a9598496760bb6c09830f5c0a87998e37" + + "5f98d13d2bdfbc7d8111570e88c6c1e4342f78d1d16ff13fc88e8a15eb66fa88e75ce5" + + "ae65d8739a4876ee60c559580a3dd568f9deb7b93026bae4dd955d3e9e0a229049bcc3" + + "0b4b941b8635ddc341f7a80386ad1a8ab8be9c4a30e69d2c5ddc66b06e511fbbe3cbe2" + + "af8bd5b2c02d2ff3a2180d807cae66300498093446036845a0bd901eba50fec22f2aad" + + "272390a125245b72aabdd51ce90e48fe5ff15324cad083fc4d159d49a3a8a51daba21d" + + "e3d189c41be121dbc0d1279fcfe05c75f003541e7fbd6b069c7724c877a328fe6d7dd2" + + "76f4baa102c458c722f1005927ebd2a41ee00ba57709e90fb8f4bbe8982e47433cac69" + + "6552bf1dc33760509a854e37ed74afbbe7665c857e011ce2af742dad1b2a20543bd68d" + + "078362e61c5b09f14e2d89a0fa4fe438b9542a8123097426a42a5e41b5b20cfddfac3a" + + "2b9effd3b058b95fb0eeb5dfa4af0c45bb47611a33b33211c61510897d19cc5eabc18a" + + "39d14be1b9696742c8008f65213a57e3a636472716cbfc784e29fb0400ed993af69a49" + + "7e25ceced20d6169738fdcd9693d892bfe276753be8902ad50f4619b64c838ce9aba7d" + + "aeabe9970bebf7a51d10008a570ee2859b707146423485df9b620e10f0175617e605d7" + + "4b025c0cbad92ecedb1f715c9d1ee102bdff8919b6acb360dc89dd68bef2d94da8e622" + + "feaa7d50c") + + // Just make sure it doesn't error. + + if _, err := DeserializeZecTx(txB); err != nil { + t.Fatalf("error decoding tx: %v", err) + } +} diff --git a/dex/testing/dcrdex/harness.sh b/dex/testing/dcrdex/harness.sh index be0c7f40fc..79d41d6643 100755 --- a/dex/testing/dcrdex/harness.sh +++ b/dex/testing/dcrdex/harness.sh @@ -54,6 +54,9 @@ LTC_ON=$? ~/dextest/doge/harness-ctl/alpha getblockchaininfo &> /dev/null DOGE_ON=$? +~/dextest/zec/harness-ctl/alpha getblockchaininfo &> /dev/null +ZEC_ON=$? + ~/dextest/eth/harness-ctl/alpha attach --exec 'eth.blockNumber' > /dev/null ETH_ON=$? @@ -130,6 +133,20 @@ EOF else echo "WARNING: Dogecoin is not running. Configuring dcrdex markets without DOGE." fi +if [ $ZEC_ON -eq 0 ]; then + cat << EOF >> "./markets.json" + }, + { + "base": "ZEC_simnet", + "quote": "BTC_simnet", + "lotSize": 100000000, + "rateStep": 100000, + "epochDuration": ${EPOCH_DURATION}, + "marketBuyBuffer": 1.2 +EOF +else echo "WARNING: ZCash is not running. Configuring dcrdex markets without ZEC." +fi + cat << EOF >> "./markets.json" } ], @@ -197,12 +214,24 @@ if [ $DOGE_ON -eq 0 ]; then "DOGE_simnet": { "bip44symbol": "doge", "network": "simnet", - "maxFeeRate": 20, + "maxFeeRate": 2000000, "swapConf": 2, "configPath": "${TEST_ROOT}/doge/alpha/alpha.conf" EOF fi +if [ $ZEC_ON -eq 0 ]; then + cat << EOF >> "./markets.json" + }, + "ZEC_simnet": { + "bip44symbol": "zec", + "network": "simnet", + "maxFeeRate": 200, + "swapConf": 1, + "configPath": "${TEST_ROOT}/zec/alpha/zcash.conf" +EOF +fi + cat << EOF >> "./markets.json" } } diff --git a/dex/testing/zec/alphawallet b/dex/testing/zec/alphawallet new file mode 100644 index 0000000000..01c26e156f --- /dev/null +++ b/dex/testing/zec/alphawallet @@ -0,0 +1,127 @@ +# Wallet dump created by Zcash v4.6.0-1 (2022-01-05 22:18:46 +0000) +# * Created on 2022-04-03T22:03:33Z +# * Best block at time of backup was 403 (0094433b5b3c527ebb052e73e3476b5feb1299919b63e4da4f7355b7e21d6b48), +# mined on 2022-04-03T22:04:11Z +# HDSeed=7402572ee8dc902ac5d0465ac1715f01d778e4213a89dc3a4f8676439fdaa44d fingerprint=66b19768d4e148f847f2dc09c4aa5427116042c0288db17ada1895c6194054a1 + +cW8mjSsvizy5LCZwGd7vmNDghKFwwobp7YEn2fj9KcpKxKXZw555 2022-04-03T22:02:54Z reserve=1 # addr=tmAA8EAagJB9xrjpk5ZQ3zXNQRPWKpK4R2v +cRH3W2C9izkcJqTcTaXZw8R3yc2opZVPUccBuJUy1AEasqYKdFcf 2022-04-03T22:02:54Z reserve=1 # addr=tmAjjCK5ZQGuevUDWGQMw3j56nX5nC6qk8e +cRtM4XEEPtwp15U3QKoEdV2C6nPipwNDrpRgbRcqjUDXudsSLpLW 2022-04-03T22:02:54Z change=1 # addr=tmAvoPy4pt9g1NuCir3t7iCAnosKjmxKeC6 +cUwPr3tr1iVjPVzwA8nPCEjvKRAYy6YFrEYS8WJGnmBZEPZW3Cs6 2022-04-03T22:02:54Z reserve=1 # addr=tmAwtbswHiSjzde3Js9xEYE7VuCqWaaK1Wh +cN19rm5UPn82RsvHTB1iUyNywhQFKQebnd7V6xpaRtzsth7FdgWX 2022-04-03T22:02:54Z reserve=1 # addr=tmB3zQCR1Axyf89Sh3RCAWaPgzYTmXpC9P4 +cTF7jhSpCGGhspVnksY9hHc3vbMZhsWAF4cKEzo4DfF89vwCUfCZ 2022-04-03T22:02:54Z reserve=1 # addr=tmBNGdKy6pzd1FAbecBxGQZkn9b3D1vynKH +cURa3BryVFbVEZgMb1ueuLgEzyB6iuPE2FSjg4sAPE5BdWK75MiY 2022-04-03T22:02:54Z reserve=1 # addr=tmBTXauMf7enmiiBnMf8uwam8H6DDsm4MjM +cPFGThHQzwFm9PapahR5kW2QSnSJ5NpSEfu4tDNn6AEw94GzChKn 2022-04-03T22:02:54Z reserve=1 # addr=tmBXobUh9M917PxrVCbfj7ARJUsuC7NVLUK +cVDuXmhcWWNrjboNuuy5qw9XtP37qZHiBtGgrbk14MXAeo3mC9HW 2022-04-03T22:02:54Z change=1 # addr=tmBa17E5RMDQr9SXu2pqsJE8iFKHReG4ypG +cRw5eHUvvm7yi7ZwQMwTqRyEFx9q2rCVvUJnJLfqx21QELwMjEU4 2022-04-03T22:02:54Z change=1 # addr=tmBdMGEW3YfZeDpQ7ivwtbnCg2EJcCernLL +cQBeK9XgPsNTqqWkio5FNtF9wASZLJ7NkRehYtShM8qkJLTBDkbL 2022-04-03T22:02:54Z reserve=1 # addr=tmBfmMN2AddEYgmRHn5H9fqTfvi5bkg2oQ3 +cRMnoonVsy3Ps5HASwkXGwPC7pn9pHyGChrrmWPNVExtWpiZGE5D 2022-04-03T22:02:54Z reserve=1 # addr=tmBuQv4gi6BM9aAU7FEQYj4ZnSHeh2oCfub +cUwMi9pbacZ4bwW4fVx2vmpgrgycgWy15uJ5Rhym6UHiFrC1f1Jk 2022-04-03T22:02:54Z reserve=1 # addr=tmCFsgiKje5vyFr1WKk6KCMLzah4jkhsw4D +cNLFfMBTahe7vdqDs7jtDDmsUwEiCGE7i7Hr6i7xi1f6btgsKAU9 2022-04-03T22:02:54Z reserve=1 # addr=tmCqaqWme47WU2bQLyufsSBmLzzamRbWXAU +cPRxJbN2urXoqrxrSy7nw2qkbXJsbeovfniiNfdZ2tELgyEdqU9h 2022-04-03T22:02:54Z reserve=1 # addr=tmD49nMtCYwMyFMDVj3DkJhLxPMQ9GTCTv3 +cTWCTq3Yqa8tpN4A6r6UpMgAfpCdXy7F8EWFFneNwPkcVk2mXkG9 2022-04-03T22:02:54Z reserve=1 # addr=tmDFo6yHtafm6qKGGc4crPk5vnbBb7GhshS +cThonMskDcyVSazXbrZ9Qm8HvhL3Be8NG1mnyrjEYThyhgsrvVtW 2022-04-03T22:02:54Z change=1 # addr=tmDUxJ4WLgMZkvRkRPEXqQ3M6fVQhAzbsRW +cVyxQqES65PwkjerfHPdMezXLkBTLntngYdJhRo8ryB6mzqxov18 2022-04-03T22:02:54Z reserve=1 # addr=tmDY2ERgrZ6ZmaVcSyRAWhBhgsuBVyXWFur +cN9yYxGz2p9MzjV5drdp8Kq8yoqS5pV9jvZRxYE7RoV96xfUTuCB 2022-04-03T22:02:54Z reserve=1 # addr=tmDZfpMxVXypRNGxLFBpFkzeLDV7Qju3tnH +cUiCxbU3z8etb23aLZ5hh1DpPWVpYxiRCePgZVLe6QLcFMvMCd59 2022-04-03T22:02:54Z reserve=1 # addr=tmEGdarBdBmH2WLWjMDeYVt6gcCoS5Sbmnj +cQc5MDXhaQZHLm2NuD3kMveugCCsNpHdWvHxGRXvt8ByCtvCz3AD 2022-04-03T22:02:54Z reserve=1 # addr=tmEJ2QFb17brkqavPAGrCoUej5AhGmbVQ36 +cSJ6WNgj1WeaB7gucPkeTa6wmgthvbzk9LLLhm4WHncf1BcnYgZt 2022-04-03T22:02:54Z label= # addr=tmEgW8c44RQQfft9FHXnqGp8XEcQQSRcUXD +cTenFH3HMdRgMXv6HvfJrseEfjheuZjAAeqCMx1KVKxJ9z7wamFG 2022-04-03T22:02:54Z reserve=1 # addr=tmEnweqHrA6CVwsmDNve1FLFGU6XvBtyXPD +cP1yZjeYAt2BmAFFvSdhjbo3GivrG3onp1gmX9xYyqBqs1wp6ggG 2022-04-03T22:02:54Z reserve=1 # addr=tmF8MGYfae9trjG56hLXoMB6vXaJHX98ERa +cPTRWFUBT5GpReLVK4JyPXQRuMt38mB1uwmNhJsfXGpxuDdrpMzi 2022-04-03T22:02:54Z reserve=1 # addr=tmF8kkE9YRFJG1Zq2nLS6hsjZpNbpEx2aDy +cNXmGYfsAe4Eh8hkxTjQTtjaNMejLeUvgMudapL5RinPbtth7LPx 2022-04-03T22:02:54Z reserve=1 # addr=tmFHhn5JarW11rKxdANjS3WvViq1ga56LJY +cPaJBh25JsBanS2yFwJZBs2Lz74ooKQccA5sFvkMwCvwBrQ7ma8Q 2022-04-03T22:02:54Z reserve=1 # addr=tmGAN8qPhZdr6ETvE6odsWB542XHPDpJKJS +cVG7GThr557XWME4omQ2sQnaKsZwfXUUEJ99eMwL87Yft9wvy19U 2022-04-03T22:02:54Z reserve=1 # addr=tmGAU5CuAJgD2cDUhPJkXEi4FvgRYJk13va +cSSwR9g1hoLmfwKN361vJ8Xnr2pXNr5Pb5msg4KfuqRMFdGjrwBY 2022-04-03T22:02:54Z reserve=1 # addr=tmGUTXkxvqQzvXbXyvFFgxJFvid7hLt6STh +cPhCU4yATWDSuVRaFKPGqESDwzjh1MqqDRRBwLnK9HGHx7VcPw2K 2022-04-03T22:02:54Z reserve=1 # addr=tmGqU5Jc6kEnPa4kcYvNKkPNVix9UcBN8YS +cSwACDBNaJRqqxQ2vy11t3H1ydTHfuKtEsSwfj86MSfXBSowwoGV 2022-04-03T22:02:54Z reserve=1 # addr=tmH6pNCbPbTWp28XaiHHzVWE8kwFpWJg19U +cP83Tybq6adbJYA7Siv8kgmXFULAPip7LJvnYr71jZEM7eGQegYL 2022-04-03T22:02:54Z reserve=1 # addr=tmH8y9qYj9eAHUqvz77bmByfijDjpGW3kfy +cVDRYmDStXQAL5bV5TojYT9F5uAqVVteRnnqSUuZ5rr3C9EEF5Zn 2022-04-03T22:02:54Z reserve=1 # addr=tmHFoJ98K8AvAQJnFwrtKZUq7AAZhHpuLfh +cVFwGfAAt6dYP9KahWW9aNfj4gSX6bVdNYshusF9mFkCT5KNSa1o 2022-04-03T22:02:54Z reserve=1 # addr=tmHdYoaTbREiiXTxYTsbwR7fW6GTsu7RUKH +cP9Loksj3wZ8AA7KD5sya2sQrc2rYiP2joTZZCxtA6k41BRxWw2y 2022-04-03T22:02:54Z reserve=1 # addr=tmHrLeirRusUKM72T2m6ebtCcwquyuvG9wm +cVfBhZyYP1ANSXVMpdYPndfisvLGrPAVDpTbjVvvrJRd8UvCFwxt 2022-04-03T22:02:54Z reserve=1 # addr=tmHyxQZFcDsQQGKKg8iZybnfDbphXePcTMM +cNVARopBb4JdNtoa4xFPRv8Xd2j5xJBs6BjgoCNYtD4Jd4zjM5Xt 2022-04-03T22:02:54Z reserve=1 # addr=tmJ5JiMV5ncQUedbbxjKzvzmRaWEWyJYXzB +cTykEmDL4oPkFaumwPXkaZykRuHdsDCNFiwcS9HeQjrD554Bu2yM 2022-04-03T22:02:54Z reserve=1 # addr=tmJKnrz6RmXrYcxmbJLZmtVcktg4s2PJoec +cUWjKyLkroXKP9SAMmwZ7puuUgbjXR4fALB9coubVsyd9Mzrxrdw 2022-04-03T22:02:54Z change=1 # addr=tmJPesPqXAXEAHKNSeJgArGxT9b5adz5nwX +cR5PjChC28GqUcaKP4oKVDrpbLJnCkudZ6UiReCi8sJisrWS4Mo3 2022-04-03T22:02:54Z change=1 # addr=tmJVvX463nQZYEnwXitEVpzjEQWtA1eobzU +cQAprNjZ9RzeTny7tV9vCRHd7uvwKfbZByDVaV4aUUddtddfimqM 2022-04-03T22:02:54Z reserve=1 # addr=tmK3xa7sS84CKG9HhzsSr3NKDnEgtiyoWjA +cRvs6eT8pxdsF4Fnnbz47EM1GYQ276E5yJNEYdhZhidKca3t8PUa 2022-04-03T22:02:54Z reserve=1 # addr=tmKLG9XLugdWk6QnCRQ7j5EUkwCVmR1qHni +cSWwxvXEYg7kPsL3RpPzB5RGe5TM5uU6nVm7bM1HkTAmcLMe6wjn 2022-04-03T22:02:54Z change=1 # addr=tmKbPNG3dSa9zj96nHUNTjwuS4c5m4eMH2u +cRxk4wP4UbibV57c5pYvKUUm3LsopnmFonXJg2iGstCTbpAeGhSi 2022-04-03T22:02:54Z reserve=1 # addr=tmKbe3qYNT2cEsfChjEKZyEBxvJKeBsTi3S +cRcVCoQnoe6yAmQDSz7XzYTCvfU2AX4cfLrWbTzzS9E2hCQc6vpB 2022-04-03T22:02:54Z reserve=1 # addr=tmKbyEY1udWXG4qdjgiw5WAcsKwELFxfffD +cNQda7UoGxuDnQJ1PY6XndX1ZBXNfpDnLEuWiksjtAVdLjxvoLtS 2022-04-03T22:02:54Z reserve=1 # addr=tmKwkSPXM6xtfcksGcjNpyV5v7mVovK8EUQ +cQyapgkpcH1fEmRqbzHDjxQ5kTGxTaSQGNKmUKnUfDUo7kHcZqra 2022-04-03T22:02:54Z reserve=1 # addr=tmLp5oj651yYNnQGWHqApzPS4JUq6r5MeCg +cVzRVuz3kjfzKwMJu3s3HrfJJmX91pb21g1ZYBrdYHURZtZaqyaZ 2022-04-03T22:02:54Z reserve=1 # addr=tmLuuaa2cSkAkX6dPFgXA8SBDr2dEUhsboD +cQN819PrVEqrmztELFChRNXxETnc4rNwN4CtquhXyNVV4pRkwYcS 2022-04-03T22:02:54Z reserve=1 # addr=tmMJn55GKrbqmwumHX5iCKGNkNHQxzqAU3q +cTqZNCgC2HPw5Trb8MVLDxQJ7frfDJZtbxeJ3Vza2v2Gg5Ru9Hk3 2022-04-03T22:02:54Z reserve=1 # addr=tmMP7EJLqkTLC7LmCPJvwXrPZe9NhiQ2kpX +cUNc6gFEHJTPRSfYyVTKXxDRwQ8VJWpYK3qdbyNV6BQVEzNo4ZMt 2022-04-03T22:02:54Z change=1 # addr=tmMfEW2UsK3H9q9YHcWZAXawCeEMG4HQA4M +cQ1oxLWQy2zzo89bdVnPWyQjJVp16fYDQmJR7SWJoyuDfYY4yMyh 2022-04-03T22:02:54Z reserve=1 # addr=tmMkXqFEW6X6A88Qo1Vrh8r8ktw9z2MxGrd +cRN4gTh37LihJE71AC3TFZYU3M1nBqHoD2VcN4iojeiqqWMi8XjG 2022-04-03T22:02:54Z reserve=1 # addr=tmN5Nw4fTbYknEnSoF3ZG9oLZ9E8VxbancN +cPw4ZmD8JusZ9uno6pEao9Mu5bMkVBufHk9odFNRs2Vz5S4XSa21 2022-04-03T22:02:54Z reserve=1 # addr=tmN6EPLAK2wn4cDMViMiE4psZaSJk4Gw1Up +cT7jQH5HzkC2KFJRmhrxChTBrrDC7bTnuTzAmQoRX5PvEAEyMMc4 2022-04-03T22:02:54Z reserve=1 # addr=tmNAyDETvVq5fMTRxnJ9SGvvXzU3UecCFQU +cW8hj2jSjZZuYFMt4Q3TvEWZ4FToCg7H7H4Y7udYm2HFbnhMZKwJ 2022-04-03T22:02:54Z reserve=1 # addr=tmNMwJt9v3wKcNuwkeMP4vuCmCh8QHVRLQq +cPPDFqxQFqp9pNzfWfJYp2S6x3avVRoP8waVapXorUgoMUjp8TvW 2022-04-03T22:02:54Z reserve=1 # addr=tmNRWbqRRzEqP8gofn5axY7C9ekA5MobMC3 +cVmwnLreqb6xtUummcNo6sjLViceXtXUyci9SmKP7KBiiDNugjZA 2022-04-03T22:02:54Z reserve=1 # addr=tmNYcvxiZiPBDMm6xHBkyDiSVXLWX6HigTz +cR5pLd8Lh1b5qboadwvsBr9hdYiDt56zBygTbpUcDm2cUxZyxAKD 2022-04-03T22:02:54Z reserve=1 # addr=tmP8L2mSiqRxFPKYutjT74mqYcExAKrJoQu +cToaMRbvDn9AtRpQi5m5iCTogXJFAwe1knx2NhCAhKPLWJ2aRdnx 2022-04-03T22:02:54Z reserve=1 # addr=tmPNwbeiuNhueomYuSUFHNJPiBRxErawsTv +cNMGF4rdNW4afxGAxDnUtW9RsyiKF8xsmuJEMefpUkKKDJTvEwMs 2022-04-03T22:02:54Z reserve=1 # addr=tmPwEhfz5aafYnzfmVqTdwPtMUR5nNLrfvw +cQiAM8hneQJCcodzE3wrehz1WLRunEatFABeCLMh1UZxeozKyWv4 2022-04-03T22:02:54Z change=1 # addr=tmQ58YFBNYsyAvLH9ZaDkuPNk9B6ab9Q83Y +cNRbMxZCkKY17oC9HEu5zdrTE1Fd7jucvN4ZdPRf1t5jsyz5EnSr 2022-04-03T22:02:54Z reserve=1 # addr=tmQJhoPGxYqUQoD8uvf68Kv6WCG4FussCdm +cTg9dowAEB8VQ5iXwCGyDwDGs9EVgTSXEBj9qbgciWTrqkN9C8pR 2022-04-03T22:02:54Z reserve=1 # addr=tmQM54VJ6sejNJn7XJv7GPn8WekDgYC8dcm +cPsoEp63xyE8Hr4c9doYpiWzskifk3tZEnuJug4MfZJmgS4g5xJz 2022-04-03T22:02:54Z reserve=1 # addr=tmQmrjGvwbXy6La5w9J8xBhBTefs2vyD1Hc +cMceYDqyqKEYZ2yHcxytBy6oKN97BtWTzzZyq6EUVi4ESfd7zLet 2022-04-03T22:02:54Z reserve=1 # addr=tmR3PxCfppNfPKoC3fEVSrWs2vpfr28bA1D +cQHouYPVZ8pnbavq9SovZXMjtaS4xt2wsrYHtrZVx52bNti3Yrh1 2022-04-03T22:02:54Z reserve=1 # addr=tmRHTN4wtSWHnWZcHxo7QzaKr5xdqCTsZkj +cUyYV1MZZD79X7G8nhsEnrm4c71SrwLetDewZHL584u9w9qpwHmp 2022-04-03T22:02:54Z reserve=1 # addr=tmRHju4agpVLyTSdTmpwMBhL4b2vJjk4pfR +cUz7mR1CayKGPmRyWAftZFKTL9LGHbSZkxDURcVdGtDGkp9adT8d 2022-04-03T22:02:54Z reserve=1 # addr=tmRSi1tGWniD9PFMdkhYBWeg8kb9UH875Ke +cPArp4NPU8S2U3tpxddHpr7S89EpAPJStzfU67axXFZ1dKoqfTRE 2022-04-03T22:02:54Z reserve=1 # addr=tmRn6caFFiv5gHE7QwDNKqrnJDFmKM52kTD +cQ3Bf7eXhu4DuFywiYmDSJzR13uGuw9bbsjmktmMQ1MhxbGZMwLV 2022-04-03T22:02:54Z reserve=1 # addr=tmSHkZZKPWNGf69GWhfGD2kL55VFVvMVw9K +cW9v3DL6KxHtgCraUKp1SH9HoRxhbvCt2CAXMDRXWBYVWPiCevNF 2022-04-03T22:02:54Z reserve=1 # addr=tmSU1c8dSkb1y1KkTVNmn1mtUVuwHwA2cRe +cNTFSPt98GhfVZEisqSDJxfvnWX9U3V73FrSWecVjdzKRU2GEFmJ 2022-04-03T22:02:54Z reserve=1 # addr=tmSkq9kQN1SAxu8MSPsbX9Jmv8J4QCNGQk3 +cPJvNpFNvRFA73tb951nBkSj7LprQB3b5EmUhwLzRaEiS4C1v46m 2022-04-03T22:02:54Z reserve=1 # addr=tmT5jhwLkTaF1VCovcQeJNpxsvgSHjvs7Yw +cQ8QzTWWKmzvy7zw2MrX6vRiqRktW4C6rmxc9DGdrBdfdm5yt1y2 2022-04-03T22:02:54Z reserve=1 # addr=tmT6PPEAcLuuBmMMtaX992b6F4qARV7wech +cUak75nEh3BAos3m5Lkw6LJ4gVnwZrEnYz44rA7tUwgx4fmfTKwo 2022-04-03T22:02:54Z reserve=1 # addr=tmTAFdF6oA9FTG7dmvecSjsndjFautfqFjK +cPqmTsLqBMJ1uMA7U2KPQLrNJNdBFi1akQKbnHJVw7gAQctF9nUD 2022-04-03T22:02:54Z reserve=1 # addr=tmTpovCbDEpWbjcwNEPQiMXytifjDZ1F9mg +cP4zoxfKJdr4fTeXopb37DKXvGMin2RzeSrrTGFWiGr9snqendYL 2022-04-03T22:02:54Z reserve=1 # addr=tmTtkFRyT1ri3NW1p6dqqx55YAhaGLsVcv5 +cV1CqZuANrHsmbD8GaCZR396fAqZJYCxNxQi2cdGGEfaqWTPzC77 2022-04-03T22:02:54Z reserve=1 # addr=tmUf3j5E3YPdj4LjytywWvLkjaWB4apmAYP +cQinX5tdQ6HcQqXdGKBtNBi8T5vPpVL2cwPxnraTzu4tbptfu3rk 2022-04-03T22:02:54Z change=1 # addr=tmUk1Uy16VnyznoQssRcpegaJZfoch6Pjhy +cQANuv3St8Pbmhr3S2NJzBv3w8T2ydxZuk3qMhJHqJxoV9exu4Nj 2022-04-03T22:02:54Z reserve=1 # addr=tmUtYEAno2es2ZysQuy6SLKmwnUVPozyPK7 +cNCtnm1MAr4hfHs2irvMpajwE9HiRuNbEyBioemzEfGZVYzTZsrb 2022-04-03T22:02:54Z reserve=1 # addr=tmV31s5jkddcGVpaECoipbpqQ36zvC1QsUA +cQJYCUeUNC7NmdCcNCmxDhSSQ8TZmSqQ5GmnxxFfyBHpUX6k6zhT 2022-04-03T22:02:54Z reserve=1 # addr=tmVG3LTZEq8p2ajq87CUFMHzd2Gog2vnWaK +cV9T8GChtp17vsJfx3x5XNyJw5w4AXcsXWDA4WqG7iezHgz2s73n 2022-04-03T22:02:54Z reserve=1 # addr=tmVMLbQnH6UXC2H1rqTsVXkcM5QoGV46Xn7 +cVNTQpHJrxMB2MXZB8joTrHhBhgscxyXiwNApRPYde1pak1ufkbj 2022-04-03T22:02:54Z reserve=1 # addr=tmVQCR4x6hrrrbGTRu1KG8f3A33CiMcazua +cUUbvZ3Y6FNHrv5ZhzH941mgEadrRKTd3ASD9EAZX1PJ8wfzMxyE 2022-04-03T22:02:54Z reserve=1 # addr=tmVR1UgDBuPDk8q2p7zDeYzxWBJ3zdYcwFb +cMyX85X1Wt7rvHaeFZURXNHXf153ZQgkBphh2ULXbTvFjgvurqc6 2022-04-03T22:02:54Z reserve=1 # addr=tmVc1Juc5yRFUaKmcSLnz9vpK9dqZgFtz5o +cPKGpnfzBGzFJrKQYsB6BR7i3ppHsNYz1nc5oqYzsrJvsMNqghF4 2022-04-03T22:02:54Z reserve=1 # addr=tmVeM73HCT6zaR5TGxbEYiDXGXwPhVmT6BQ +cNa5gYmpaNaiE6sJneMDrKYvi67K2Uum9VUFdSg5jbdTWZY4eZHe 2022-04-03T22:02:54Z reserve=1 # addr=tmW9mwdM9brCdiuDCcBAB6Y6DFqnjq3p2nU +cQURPoWzFbyTCdZLchrBMSsFQqRBjeXmWiieHiPb2AxNbatmLqhZ 2022-04-03T22:02:54Z reserve=1 # addr=tmWWSApp4RqDrASSrkBvbwVjPmnx7uRiHWw +cShhpmJ179ufewWFKsoPB1LvMumc1wVkqL2bfEnLr1mqWmifBNDD 2022-04-03T22:02:54Z reserve=1 # addr=tmWgGY1raemBq7G5KYhzE9ZroYDzQPXpK8d +cNKyE2wPX86PSDdQvJDonfYvs56S8MzaqFuwFL7HxaYPSNX6JS7i 2022-04-03T22:02:54Z reserve=1 # addr=tmWpwpTDDeZ6j7MEJFMgpHMJjaadN37cewq +cPHPH8T7CxtkvYepgs6pdmv2VDozdqKh5XUZJZudC73TwQU6xrGK 2022-04-03T22:02:54Z change=1 # addr=tmWrVzgga41Rg9TBZ1TujhCu84a6T5N17bx +cUGYuTYq8KeVEs5eXKQcwQPiZVJcSczBHZPhhuf1QFtwUrDCYsRf 2022-04-03T22:02:54Z reserve=1 # addr=tmWsJQLQxoBWZCjNEwCrwAdUUJyZTg2Tai1 +cN7P6WqaJ1oB9pMSRrxkEN89rS6RSiHnviLpQJpyYmxi2ucB8CRF 2022-04-03T22:02:54Z reserve=1 # addr=tmXGa46SVQD6W1sCETkoMkiyZbRSrYbjYkW +cSXCijBWsb3TJRrp6Bw5cBCH38zF8oC8ATCfsF39QtZ3oeTKG9cU 2022-04-03T22:02:54Z reserve=1 # addr=tmXWmuJdkaULebBLpoAf7gCZ3KpJT8FamyJ +cNGiziWAqtimfbfezrisZf6uLfsuBm424wvmkJ1KHupj9bgu9Bzs 2022-04-03T22:02:54Z reserve=1 # addr=tmYXd2Vn4cFeE18aHqUYGhGE1E7zvAWNGpH +cQn7pLNDDnYec82LYpb8oiAe9dW1AuwNgB4iYhNA1UV1KUgnfw6N 2022-04-03T22:02:54Z reserve=1 # addr=tmYbgLjZDfhuVQuohx44faksJQBXcPGzEfW +cS7Qka8xXo6e397hpsq1N1GGJykDN4jZfCwANtAA9z2Ezk7w2kR6 2022-04-03T22:02:54Z reserve=1 # addr=tmYnPTa156K3LSkAANvSBQiekgpVsQk9ouv +cRwUCaax9wACbfWrXh7ERN5Pz7cKuzJrsgjDpLwzBM6JcRrmkCdw 2022-04-03T22:02:54Z reserve=1 # addr=tmYo6jx6RgLBHurk4MqVAYoXtwGqP1sNoHz +cPjEAYSKcdnoaF8mvqS32WyyUU4saznPN7zX3JrwFu3fiTov5QVp 2022-04-03T22:02:54Z reserve=1 # addr=tmYy9HWJckHVQMgxkj7ZaTpYimdx9ys11jM +cPTZcs779aReZc44QEgaJ2LcVnhMLuMyyC5tMnzfigpjvGxn3zSC 2022-04-03T22:03:03Z reserve=1 # addr=tmMwvwbFYDQ5t9Qo5JioENDBpWdZQ8q72om +cUPuFKauSkzYj4mqc73Zzyky3Gj4n8Wk9rGS1Waart6wp8BuVV7S 2022-04-03T22:03:04Z reserve=1 # addr=tmGRpJgUnqWWF5axLHMEJBnVmNYyrFR8QbK +cRfPVHfETtgePJ2iyKe84UzLMPsrMimPxSsS16wUVykpq3QnoSey 2022-04-03T22:03:09Z reserve=1 # addr=tmBwA8bzTdg3duxFySbed4NUGJHadsp2Gcx +cULrhoisWPyyg9MjnCq85bHP3nZHfmZQcy6wWagqDZ9kNo9SkRPe 2022-04-03T22:03:09Z reserve=1 # addr=tmCWvnnKj3RhQuzvKb5VyDuRubAMynPs4f8 +cUHAVVjhtSqJWPuRYkvvxwgQFFrZrn1QG86u1ohg6uTnXrM37J4G 2022-04-03T22:03:09Z reserve=1 # addr=tmFKkcB2zPaFFtaVnqwzZqtuBGsGQzh88Hg +cRL5zrap6J9tFr6Kx83GZTxuJsNvoGY7wKhHtzegsP2TNX3bsaVS 2022-04-03T22:03:09Z reserve=1 # addr=tmHwSjJ86E1jJejATvTfRoiAZswvb7JTzEB +cN3HTS2i1ySUVNp3ghn25MsDy2wFyRc7J9Y3oDfCkXcTcGXb5ArD 2022-04-03T22:03:09Z reserve=1 # addr=tmNSgJExYz5YWE7cunhcizpXCj37hrZysvk +cVyqiEQsoFudgkFNoNC1aFzNcq1ZcUErPFWunisc2QohuLutuBjc 2022-04-03T22:03:09Z reserve=1 # addr=tmPWSQ11UmYMBn6fxU3fE7dWSM5DucgHwnY +cTZfj8oUeWDMR8iRAjuVjrzTXA8AtHLFCofCLqVxv8WZyXYMhuiN 2022-04-03T22:03:09Z reserve=1 # addr=tmPyx7WjcomsVLdU4wjUkRAE4XdHAMJjas7 +cS9u1Erc6FnUBRgEGjkRxXBcfiLgg5ba43a5HtTBkPmN3akHJ1QY 2022-04-03T22:03:09Z reserve=1 # addr=tmXNbm4SMKdmRxpVrtU8gVVR54euw3F26LD +cPkuiiGhRoCa9ow7pDRqt7sWGm7tJQnF8WymzUn43BYTnPHGG6uW 2022-04-03T22:03:09Z reserve=1 # addr=tmY5obWQEPcmCByufJsHjtLZ3rzzBATRJRw + + +# Zkeys + + +# Sapling keys + + +# End of dump diff --git a/dex/testing/zec/betawallet b/dex/testing/zec/betawallet new file mode 100644 index 0000000000..7c05a10c7d --- /dev/null +++ b/dex/testing/zec/betawallet @@ -0,0 +1,116 @@ +# Wallet dump created by Zcash v4.6.0-1 (2022-01-05 22:18:46 +0000) +# * Created on 2022-04-03T22:03:49Z +# * Best block at time of backup was 0 (029f11d80ef9765602235e1bc9727e3eb6ba20839319f761fee920d63401e327), +# mined on 2011-02-02T23:16:42Z +# HDSeed=f86ff289bab2f517c91b64f6aa293049e00d46aca8b3d3fa40f3e88080d71c76 fingerprint=b68f17e81209624e6f893f864230f462029e2e1279e296425fddb3a68a1b22c4 + +cRZJ5tNTmrh7pBmocdnHXS11wyaa7vLaqsunq9cNBpNAkUvmiBhz 2022-04-03T22:02:57Z reserve=1 # addr=tmAPrZAY8yNA17GhEcEPMUEi5e1ovn2iymA +cPhm1QdjC1AWXM4nZFzpJgXXabmKRGR4r58Ek6Sw34H6xBMwZ67w 2022-04-03T22:02:57Z reserve=1 # addr=tmAU3FAJYwNxTaCmxCEaT9Ju3EcumbRKAoX +cSRiiXVoduhjzqSGva24XdvivZS48mRVcQzUUpTY4BhdC52Ubi4C 2022-04-03T22:02:57Z reserve=1 # addr=tmAg1oeQxfCtp8mbSQpBknm7Wm95AZdyDzb +cVoSn4kZaF4NeG1112tYd4cuGZPL4ZLGwoT9hpBuNz8B3XtXAKJx 2022-04-03T22:02:57Z reserve=1 # addr=tmApSq8VWAEAN6ZT6CpPEyx5ZzBhcZiRgGb +cMwJYCPV5ooVp5RDVYbqxVSKrg6GbhkAcgtJRK8PKCULU1YMmu25 2022-04-03T22:02:57Z reserve=1 # addr=tmB4EdVvH3EV9DkhzpPJxKe6874ZMvuWrHd +cUuDYpxSpJPDK6X6UUJWGVcVSsdCeyY6YQAyKUiyFjLFsVzrmzGs 2022-04-03T22:02:57Z reserve=1 # addr=tmC4a4UjsfJF3Vufo8W4QTxuaUcqgqU6vk7 +cMshriZVNtVRcvxrNXjyDLfqsEDUTjB5CAttofJXLbFrMZNcmguZ 2022-04-03T22:02:57Z reserve=1 # addr=tmCFjaKUNdim8c5azTZNRaaoZ3HaX8ad7CT +cTqJHPVdb7PVCDziLE9souEZYRrbSCTqzoxs6qozc7m5Ze87DD6j 2022-04-03T22:02:57Z reserve=1 # addr=tmCQijuQa2gNnztyHHS5kD4uFxSutzRYbuY +cW1AQCKa9RaSpvgpPdPUdddBVHj6kdjhWM93QpjFuCqz9wH7fJKj 2022-04-03T22:02:57Z reserve=1 # addr=tmCUL9VKoUuJwn9iLbrTAhVHkA6YJgJYXmW +cTNEiRFjMsBC9BFTxMeeJ611G2WZHdiykc7mjbVDdxg1j21qoe7i 2022-04-03T22:02:57Z reserve=1 # addr=tmCbPCqWFxkZq8sTYWjxZeZgf7yRFG5kf1Q +cUJzatcStMo4eqneDxxgTWoAv3v23HZdrQvVXir3riM8mGHD9qKY 2022-04-03T22:02:57Z reserve=1 # addr=tmCcRjtCv6AJA6PHPb3oCkwbSAZr3JaQhn4 +cTmY6B5t5mKgBGK3daxwMRqbqnGPp81fjsKNHsNn5pTUhGKBYzZh 2022-04-03T22:02:57Z reserve=1 # addr=tmCffBCXRdZqGjRx4bKoJsZ9UXZB51W73e4 +cRngQgdXuwUGD1XPs8dkGhwj1PnnWU1uK4wxVZjzGk9GH6UmmobN 2022-04-03T22:02:57Z reserve=1 # addr=tmCmbUzXDS8U5xoAvybE6uUgnZ3o39UwTsN +cMqVi9taQUuEvsBdd89ag2fw6QCRJNVH9gcsvCvFKFEYP71J74fk 2022-04-03T22:02:57Z reserve=1 # addr=tmD3J8c2AsNVpPFR78X1wMaGbbrptPsuS5P +cSkb7qvHJAn76kjv4ypztwNa7uLxHbtte4sbpePj2XccDraP3Mi4 2022-04-03T22:02:57Z reserve=1 # addr=tmDWMj4xZRA1ytiu68TNCAEP7t1Bm4YYzpN +cUACMSTrRaKwmP8ehZKnnPt1yyJkAcZhcXEQZrDWRxseBSUWfBq1 2022-04-03T22:02:57Z reserve=1 # addr=tmDkaaFvxCXLQiRQAz7fmLBZZnxo6ddFXb9 +cPRHSfszCWaJpp7uSpTj8howUhdQVwKykgXAtgGrE1G4PKPvzpLe 2022-04-03T22:02:57Z reserve=1 # addr=tmDztU3VeygScwzPyTyf8e9GVJKEpkskxNE +cQAR5eJZbcQLuM8NfK9FaFypiXg55aF6qR7ij7TNMwm1MfYYtyyy 2022-04-03T22:02:57Z reserve=1 # addr=tmEBgSVrAB2XiCaUENZA1yn6r9meLXYtJUK +cNUoumj2144mquh1Y1ZYTJjuJb4UqW1Bgs7w1sAvfFXCPK65vNWX 2022-04-03T22:02:57Z reserve=1 # addr=tmEJWfmP2BDCbGST8YzVcCMuU9mHTAwDRnw +cVaJN4zeA9Pfxp3QLRQ6pkR2ddzH41qycz6NxbAnCWhfzc6QPrjJ 2022-04-03T22:02:57Z reserve=1 # addr=tmEMFMnJ91f5oapSmcDSPbxnB76RGKE2EFw +cPPgw5apkFjhnvFtvTHq3C12eEQXoDwRh3wS8egQzqdmCKi7PUiw 2022-04-03T22:02:57Z reserve=1 # addr=tmEMhmiW9be74v49wbN5hWvwCqp8bu7DXu2 +cPjKLVWUjouAnHMiEomX7ZP93fbU2u3e1eg8ByAmmvaZdFXkjmuL 2022-04-03T22:02:57Z reserve=1 # addr=tmEPABF6mGrB3YkS97nQ3X4hC5DjdanuRhA +cQEgPV7qv7ofdFFLR78nk71rZKiL27hSxZWofqh3rWCoyiShZ6ZK 2022-04-03T22:02:57Z reserve=1 # addr=tmEgQUztvhoa7ffmD4EoSBMVmLwTUEeadiP +cRd2DW5BL4exYpLtGVNkdTPSjoFg32B78ps57TiBDArEUiaazcNt 2022-04-03T22:02:57Z reserve=1 # addr=tmEpF167DGMbNriSn5PvNyMo3AWTH49DxcU +cUtugmP3StBL2XpZ67yG6jo16FjX3zaGwixk3tNDBzjaAMgLQSUu 2022-04-03T22:02:57Z reserve=1 # addr=tmEurBtdUAwQp5TEwApbTqSyJGLR2WtEYPv +cST6ptgJe6erLS8NW6t3qC7PsZQmvKronve9jpqthutREWrC45co 2022-04-03T22:02:57Z reserve=1 # addr=tmEvJi3FhvUSTXW5B77dbFyg7Z9NVyMge6E +cTSFyETdpHnRoqyvn229sCnvWTCgAuCo1JGZ8VVYu1RS2fHNVynG 2022-04-03T22:02:57Z reserve=1 # addr=tmF89Z4qFiqNK65vG5LzYdn9NVXrj3eXHF8 +cRK6W4D1keVh3U9xx5wonZ8aL1RJUr3qjzk7QSYPuca2p1znrnwf 2022-04-03T22:02:57Z reserve=1 # addr=tmF8FqVDyvSSVAimws2i4a2Rti9swPoSjzE +cUBEFN8WdcENwF3tSWxAHmq1WEjGba8R2d5XaWVYEGkRccwCjYRH 2022-04-03T22:02:57Z reserve=1 # addr=tmFBSmYfZoiD5rPUJ82mYPu7UKy5h5cm8tt +cR7u39dtKpXADdhqYRm8McEaCEecQYf4SziwnjEQtMY8JSsPNaHV 2022-04-03T22:02:57Z reserve=1 # addr=tmFMHVrmsXoDptH3qqxsdL79RUeL95WxA3i +cS6Y29PuXF4HGeaf6LCbxxi7fQgsj22JQYaus1Hj69VjcanhN6Qw 2022-04-03T22:02:57Z reserve=1 # addr=tmFRUx5DveF52fPmdS6zwKAAmPeUxvT2wgw +cVr2jm87tsocSs6YebhubLZKFQqSQTT3VXgzCWMSX7C5xyc1HH8K 2022-04-03T22:02:57Z reserve=1 # addr=tmFescFArSoZnd2b9n3d3P8ACHwu2yYVXS2 +cTy4Bdq4XM3mAEVrEYbDozQF6RyhUVRUKTAnGybTSLABZCzaSRZe 2022-04-03T22:02:57Z reserve=1 # addr=tmFfDoCdrZPX1o11BhNt2ceFjLueY9CX4TX +cTxDtMupF5RhkLWw7eA7MeGCXusp7QBhKbgpYaRLjC5ceTD1GR5M 2022-04-03T22:02:57Z reserve=1 # addr=tmGfDffbdaXNA2zePJwE1oXErdFnxywbMiL +cTuqRyN8TJpjz6y9Mvcj1ypoXd6ouERgewxtXvVXna9pY1PuXSTy 2022-04-03T22:02:57Z reserve=1 # addr=tmGrLmkBqjZy3d6grpu24JtswuLZDTL8r9p +cQm5zaH9R8DgatEoZoXmpcBifUR45prPLPBCZbHMbanaQTCixEnp 2022-04-03T22:02:57Z reserve=1 # addr=tmGrvTUs12KpcGokZq5p8NGn4GhawxEYdw2 +cPaEyryuzet4iLRTWXCYTbFjmvEvYThi5DQex3o3SM2xwU1HZ8Mk 2022-04-03T22:02:57Z reserve=1 # addr=tmH4bSUJYs5UtgVBFGfek18y3h45RuE1ZXo +cMdGYHcVTFE9G5z57aPmZcGhbQz8E6G2nEy6ChDajph4mPXXKfFY 2022-04-03T22:02:57Z reserve=1 # addr=tmH4e72psTuD6KskVpVzRrw3ZDL7P6m6ch1 +cSderWsaNNcwJQ8EFcoW7sezK4AzNJy74oqqaMrppssYA1zh3ayA 2022-04-03T22:02:57Z reserve=1 # addr=tmHRe7usLgZU77wzzisV9WzhuU6JLZm9kYy +cP4UGYNo8F7t6iMe5RtyLqLeWPgEzS6L1G9YwnZ1VYm9ABB2o3Y5 2022-04-03T22:02:57Z reserve=1 # addr=tmJ4fsf5qKy6kKw7kpT26sd157ujoGRKGa3 +cTJwAcgpR9nXPa4veRRkn8FyJiuQuDVsgVNE6fPyu7RjcLGm97dm 2022-04-03T22:02:57Z reserve=1 # addr=tmJNxoqmHHVKgkcyZqqikqYgESodWJH849N +cSCpuauhK9VbFjFWo3iQB89qwVnBBKDb1RwprBdGD3SMviWv1BJM 2022-04-03T22:02:57Z reserve=1 # addr=tmJSrAWQy4J5cmb585MRqwHCDcmrSwsDYmj +cUUsvvnLeuyVPP59fYyjbrmxGC4ov4sDGxjGYVJ6WjCJ1n8aGRiy 2022-04-03T22:02:57Z reserve=1 # addr=tmKMFq6sUjcNFyX2hL2BBQ6cQCp5X9svCEN +cUWVYvu3gRcvAyt8jqbmGzeNVLoePGAMvXitroy6mHQ97C2sRiDx 2022-04-03T22:02:57Z reserve=1 # addr=tmKpVsqCoW4XfCvSwz4Tfwg5qoEyvPf5deK +cNj5iGbie8Kgfrwc63xefncEGKxwRsYq5uNqkkNhNxkTxFRedevU 2022-04-03T22:02:57Z reserve=1 # addr=tmKqDF8SdfHorbGgoLXsSZSdK4YVaZQKpyc +cSmiv7Q9aTKcwtCatQnrnjtn1uGSDCbwgSESduUqPyhRUx533vNU 2022-04-03T22:02:57Z reserve=1 # addr=tmKrfvJjw5tTbUwMWdELg5oJ4TMe3QK5f9L +cSCTKVTsyRxWLpLYzizAJW4ogHpquiohWPuAujopBtFxV7G3sm1j 2022-04-03T22:02:57Z reserve=1 # addr=tmL23jCoidKwZysCWEs1ube6Dv6JG6xT1WE +cV4F8D4DG3NBhNW8F8Uz4a9bcXBrc7MWQoVdeqkhwhYWa5aEhWPh 2022-04-03T22:02:57Z reserve=1 # addr=tmLfUyPQN7it8WtvN7qobv8HD8ij9g1dxjv +cP42Gd6BfkBXhXFiNHUQTdNv9Cm5GdtgzXVqT8SFWw2jJRwdPBd7 2022-04-03T22:02:57Z reserve=1 # addr=tmLhnuhBSB36DPhupssEM6k5fAbk3VkNyFq +cTghSxkSqXt262Px67cKXyWyY4bwDYeY1NdzXR9Zx6ZxxuQM8VJC 2022-04-03T22:02:57Z reserve=1 # addr=tmLpfk9i91Sq1QQnGRDZyV3DRUEE7e4J8rM +cUoV9V2W66Nf1iHVAYRYgWrkwzxnPWveMgZ39P4pYxTS2MzYM6oN 2022-04-03T22:02:57Z reserve=1 # addr=tmLq4wL1jAeYfvkUz6qxQMxaiGJwKqfcBop +cVCCTexykRqDStPHhyWhw1whvTSL6dehFDYHCyC67jZUgUQMBc1q 2022-04-03T22:02:57Z reserve=1 # addr=tmMdmxgxDrSsRsbJmB7YwivpFobRufEHzLr +cTFicLKBudiKTJwHVBBXpUsRZ7Arban34LjKPdB8qoFdXianaUT7 2022-04-03T22:02:57Z reserve=1 # addr=tmMpB8Rjt21tX9wuxqxFASH1wVVdTxAKv6H +cUm6mtizhDNSjM4xx79Y9P7PNvtJWWGRJerQpT6dbvdDyY4NAfyy 2022-04-03T22:02:57Z reserve=1 # addr=tmNEvuGmayDnu5LCkUomMRi9aSEFxDKnnLp +cQX17a45cMDJRRC5o9CZGxShJjTwWcT9TUk3REnPkA6mxnVcMUWH 2022-04-03T22:02:57Z reserve=1 # addr=tmNagoUJ4zdiY4V2yox6hXwR5CAeQDS8jrB +cRuPpshQ2KrNX6Jv3m94iZMmNawDvggnfCqWNDgDn1sRYAop4WJN 2022-04-03T22:02:57Z reserve=1 # addr=tmPS3H2qTpEhD1W5ezPnsPsaQZ1DSPVmzJP +cSAdjyEcAiEqKJy5ubnAvP8F6fqZCTJCF8rdUSAXbXAiSLTSZphe 2022-04-03T22:02:57Z reserve=1 # addr=tmPTZR915oVSqj6xVULHne9TyNmhmNH9faB +cMuexCTXB8qdYgX6KQiGoPJDvdKg1Sqq6j4AFkakLDNDdyc6haLT 2022-04-03T22:02:57Z reserve=1 # addr=tmPqBX9ThS9vnX8LcvyPsDPi3kyesD3aimH +cS7Nj3jNvc2cmNP7qUirAqkX2k6fpPYsnPuPyyqGC9Jxs7dZxuvw 2022-04-03T22:02:57Z reserve=1 # addr=tmPqF7sZYpaBZgSK9EAT4FrdkvEGepcGf7H +cNPPvHMMWPic8Tw2ywP8Jr9PCYWFFMPXDsZepWqd8AnuFaRT7Ym8 2022-04-03T22:02:57Z reserve=1 # addr=tmPrVvWTtmv8VPz3ysG7JZVQH6sRCkhjpfJ +cPZch5z2Br19QvaVnezjqjMtaEJXZnJ6Cu9M1XapAfu5MMgP1aNy 2022-04-03T22:02:57Z reserve=1 # addr=tmQ63JGxzzqTvD6Hp1BEL2p8FtbmaM4Xmmh +cU48FgCMNAUei9dtJezpX8btk3HGUUNDrVPzboiCMzgNaP1rv4Hu 2022-04-03T22:02:57Z reserve=1 # addr=tmQCVay5Li72X1SQ3kdHDLXDSfDsf9Ggei4 +cRjGaPV4duL3dvvk8AAhtbA6fwKZGAkujkZpRzPJfsHB3pPrHt6P 2022-04-03T22:02:57Z reserve=1 # addr=tmQKXwhbPK8MbT4P3gVtZXvR6vYq56mKuzB +cRfg7ev9Ci76NroSnTepVAHAERbJnNiEHF5PLkKsa6DftT7AaoxA 2022-04-03T22:02:57Z reserve=1 # addr=tmQUe5D685yknFvvdQXKVczwLd1U8v9JD4v +cR9pDzD843TfcqzxV3eUaDDuVad7Bgk8vw6c8FELuQLWzqXt837z 2022-04-03T22:02:57Z reserve=1 # addr=tmRSw7PhfAXgcKexu7VpwRgjoESEijw26gx +cW7CWUuFR5UNhNmXQebsVpwiQ2E73eC8KXYyHMeog8SXk5dGdBNm 2022-04-03T22:02:57Z reserve=1 # addr=tmReBF7qKFgveMxZk32LR23cVBSbJ9aGuFJ +cMjcJgkqBDaWtgc2FJWXab5gpbM5aAVWFCB4KUKaEw7acxJWCSvJ 2022-04-03T22:02:57Z reserve=1 # addr=tmRp9mpe6NihxW358G7ahSz6BAy1wWAMGQU +cNLwZCqrQCz2tnDYSwaD6oGcgeB6f2SZXK7pf9B9E6FcED5yo8vG 2022-04-03T22:02:57Z reserve=1 # addr=tmRrTu67TW4dLqQcGFAjfyYTKqMpMM1uECd +cNEjFQYW225o7zqfnYLE9nhHMoZx5FUZSDCo5tkzpWbgHZxDtUia 2022-04-03T22:02:57Z reserve=1 # addr=tmSLyKVrWiWramrPk8RukTPQPST8R2Tug3V +cSRHfnYzJZebZSrgUXkRJqP9PAL1uCtkB2NkpdDPAjfqMUmAeFy9 2022-04-03T22:02:57Z reserve=1 # addr=tmSSA5GL7twFNc2dSp3eNVz7aXvahMahPiE +cUFWyd5ZL8obydrZC7vj8WB1hij9VfJ7pMTPHh1nVCHYMZFkGET4 2022-04-03T22:02:57Z reserve=1 # addr=tmSVAQkrUMeYpz74ag6pTiY842QX8Lcxgr1 +cUh3EF7hqnAvFGasfvA6dP5yYq4s1fZnwp3bxEA7h1geD5FstPG9 2022-04-03T22:02:57Z reserve=1 # addr=tmSeC4xLaMkhDTQznL1AAXFLm7zxBugQray +cPH8DQfinWjDSoQCFZcZVqzywyPUJ2URsmhQfs9TJaf2hKMk2Cio 2022-04-03T22:02:57Z reserve=1 # addr=tmSmp8k6oeMzLQWnJfsxUDDKSHUPmb7AS8o +cQ88otpvKN9zbF1RvEbSeyzabtNVD312fFvT73PQE2mrMNAXmVmW 2022-04-03T22:02:57Z label= # addr=tmSog4freWuq1aC13yf1996fy4qXPmv3GTB +cQrKmb7VPpucB6bP7jWd3aVngwBPG9pDivbjtn8thnYpCqnf5634 2022-04-03T22:02:57Z reserve=1 # addr=tmSxGbpzUEqZcHapKJ9GSEEaf1jatE47hwb +cT7Vh7zJ2NsXBFBD78JnuzKqrohjAAwf1yhnEJ8kGHbduPjXke3W 2022-04-03T22:02:57Z reserve=1 # addr=tmSyT5Dxx6pZYeJNRvdVPZtQ5tgrwvNqUZG +cTPeLG4ioSJbB5RzibBJDCTaCKexeiRUcZZXpydLuYLSAXfD1i8e 2022-04-03T22:02:57Z reserve=1 # addr=tmT2rfhsHHPJoUAwMYExwthCYivGfC5XrKS +cVVUGsz9SQdRTM13KM2NXZmhiL1FY28cGHmwma4FKahuoeEMvgRj 2022-04-03T22:02:57Z reserve=1 # addr=tmTUKn7XNyeoHi6mT7jffSJYhN29hcwZDmt +cULKHaBmvzj7MDDMLve75vftewRMZ5ZXY2hARLkXiCcgfTaD6kHa 2022-04-03T22:02:57Z reserve=1 # addr=tmTVS1cRozATfHfyced4WNYxtS31L259CyT +cR5bhndrNd8jCdpRmbtwidz3Ngq6UP7A1WbVbpsday5VSCt2PLuJ 2022-04-03T22:02:57Z reserve=1 # addr=tmTZj7Prt2tNk5CGrLAbHLPdv5JoLhMXg3u +cTjWS8xEeUEDMMKPtKJxC5MAUBX22sCVRb49zWyP87m7SAWssSEW 2022-04-03T22:02:57Z reserve=1 # addr=tmTnJaQL5GiStQy7JS9ZWLmz4diSJmSvCoZ +cTqiLMRHuQYsnATQfJc1mjeyZKbF22JTzbyEodoW2ypcArKP1FHf 2022-04-03T22:02:57Z reserve=1 # addr=tmTyAUi312BjSKPC1qSG5eRBPJZmzBbLpJP +cPhJUfMQ3E4M2b6MznE6hrcGmm82nxmjugHLuKygtDqmrsUJnySC 2022-04-03T22:02:57Z reserve=1 # addr=tmU479XGjx2EvH3LT1rkqGuHWFzfeDkxxhJ +cTK4yNhrih1zPoLDDMBBJhbx9CESo8AgfaqSxEhiTjEpdMd6PebX 2022-04-03T22:02:57Z reserve=1 # addr=tmUUzpFnsvRBpnMjRggCXUzmcCxMZtVFVFF +cVt2jxgZ49M3zzLceKsDouQk1mXzvXNNbHrT1HdsN8jwLmGBjBp2 2022-04-03T22:02:57Z reserve=1 # addr=tmUexJCpj3UNYZVKzPq8XQ5QMVFXRagnXGE +cSsHBKs1v5kmi8et1cJG1zdfsdY7ordx8AK65PgPcXoE6Lnx3xWf 2022-04-03T22:02:57Z reserve=1 # addr=tmUqCVYxCvUVqGAxKjXG9QXn3cUSBPbTHQH +cTYjum9xPgAjyCWg3xkTGSj3HyTPupCbqgzNmcacWv2FSGjrxAmb 2022-04-03T22:02:57Z reserve=1 # addr=tmV3dJd3gfeYK2bCMxjfEQYkvhXg4ah1PQG +cUsCFXHMCgf6gpCnpT3m76S79TruJ2HKKc7vrRLNJ6dBhkeiKMti 2022-04-03T22:02:57Z reserve=1 # addr=tmVKCAnexWnytY3K9RZyLtDTTzbyFAdRHGs +cQQzRidiEog2UYXd9Pyyg5yuDpbKh4LxzQsqyET9arcAGphHDJLg 2022-04-03T22:02:57Z reserve=1 # addr=tmVSrJjZvzLAFzF1WzWJSDdXSVjmwkheLwW +cQW1eWaqiyxPdi7a2zps3CPsnjjaApA7Hf1L5hfAbg1dX1pLzAVG 2022-04-03T22:02:57Z reserve=1 # addr=tmVx6AfdYsdArACBydJDzAMrTt6qZTwrKvv +cS5X93aEP6tenqbc3YABKPZgyqC1QVev92tjznv5F4pLf96Wo1or 2022-04-03T22:02:57Z reserve=1 # addr=tmW4gJYJpHkE27s67QSMAZYHXKWX7vLDWk2 +cNV9avCNKd3Khz5znyh96NWaEbn2jnJ2wCccw2tRVivoSzTRESQn 2022-04-03T22:02:57Z reserve=1 # addr=tmWjrTaR3ot7oxHi92bZNLKPS42ccrULQR4 +cPU58Lbcq6Yu9D7yZ7KK69UFEtJ4T3Wu2hX62iZeoVmMSDKQmFCy 2022-04-03T22:02:57Z reserve=1 # addr=tmWp4ak2vb8JBcT9xp7asSh74abTusDe3ka +cTM51sFbtaLhcHG49BrehZrHv5YdssooQQCrMD5SENXVwtFXuPSL 2022-04-03T22:02:57Z reserve=1 # addr=tmXGG44EaNHZgzBedKP7xug5WDsyTrcMqK7 +cRRRxpwbUevexZMn89MinJGCgYGdmijQzP98qKwxtHi74N3wngLk 2022-04-03T22:02:57Z reserve=1 # addr=tmXJVxV4f96S5kQjas7Km1WHGuSFaG8shKr +cSjLAaNA51nBHuKZL4ExYSrn624rrwDAMbasqcYt9bdwzkTbFfnD 2022-04-03T22:02:57Z reserve=1 # addr=tmXnXQsQeh9NzzTYx1x6AwCsUjX8S3ZnBZZ +cVS1saNAryV7LSfg4zicSYAzpYgz8CbadfPD8djKSgL7m6ii5k8o 2022-04-03T22:02:57Z reserve=1 # addr=tmYNcuTFSLE5sNdim74xjaUcuVtje1tpw3e +cVnLG2S3RFv6n5GqCXyZ8xgd72yfUZ5ictQ2C81Vjf1bR2ydWF9H 2022-04-03T22:02:57Z reserve=1 # addr=tmYj99meWN9iGpRL63gH8ksMT9ASxWjG92R +cQH6qCMioMSKBSJe5MP8v9b1kd1ty88ddUbK3FZuoDz8pwSjiAh9 2022-04-03T22:02:57Z reserve=1 # addr=tmYoaf11GqJc3wvfYgf4dAbMVaDter2BngF +cMfp8GQcs4xyXqpcEaddBDUN1HyFmjdJ7PGREXEHkJxKZJyEFAxa 2022-04-03T22:02:57Z reserve=1 # addr=tmYwWmc7M9L9wmQ7jcU5ZRvHpDTuMDoQTBG +cS7mPWVW92YyboFQq1rJMYPjskdNtY8y5Cee73uWEZiiMU9qZ5yn 2022-04-03T22:02:57Z reserve=1 # addr=tmYyEXrBWJTaFGysM7gaZ2Eb2dYxzCaHZHF + + +# Zkeys + + +# Sapling keys + + +# End of dump diff --git a/dex/testing/zec/harness.sh b/dex/testing/zec/harness.sh new file mode 100644 index 0000000000..f215800a4e --- /dev/null +++ b/dex/testing/zec/harness.sh @@ -0,0 +1,232 @@ +#!/usr/bin/env bash + +# IMPORTANT NOTE: It can take the beta node a little bit to get caught up with +# alpha after the harness initializes. + +SYMBOL="zec" +DAEMON="zcashd" +CLI="zcash-cli" +RPC_USER="user" +RPC_PASS="pass" +ALPHA_LISTEN_PORT="33764" +BETA_LISTEN_PORT="33765" +ALPHA_RPC_PORT="33766" +BETA_RPC_PORT="33767" + +set -ex +NODES_ROOT=~/dextest/${SYMBOL} +rm -rf "${NODES_ROOT}" +SOURCE_DIR=$(pwd) + +ALPHA_DIR="${NODES_ROOT}/alpha" +BETA_DIR="${NODES_ROOT}/beta" +HARNESS_DIR="${NODES_ROOT}/harness-ctl" + +echo "Writing node config files" +mkdir -p "${HARNESS_DIR}" + +WALLET_PASSWORD="abc" + +ALPHA_CLI_CFG="-rpcport=${ALPHA_RPC_PORT} -regtest=1 -rpcuser=user -rpcpassword=pass" + +BETA_CLI_CFG="-rpcport=${BETA_RPC_PORT} -regtest=1 -rpcuser=user -rpcpassword=pass" + +# DONE can be used in a send-keys call along with a `wait-for btc` command to +# wait for process termination. +DONE="; tmux wait-for -S ${SYMBOL}" +WAIT="wait-for ${SYMBOL}" + +SESSION="${SYMBOL}-harness" + +SHELL=$(which bash) + +################################################################################ +# Load prepared wallet if the files exist. +################################################################################ + +mkdir -p "${ALPHA_DIR}" +mkdir -p "${BETA_DIR}" + +# mkdir -p ${ALPHA_DIR}/regtest +# cp ${SOURCE_DIR}/alpha_wallet.dat ${ALPHA_DIR}/regtest/wallet.dat +# mkdir -p ${BETA_DIR}/regtest +# cp ${SOURCE_DIR}/beta_wallet.dat ${BETA_DIR}/regtest/wallet.dat + +cd ${NODES_ROOT} && tmux new-session -d -s $SESSION $SHELL + +################################################################################ +# Write config files. +################################################################################ + +# These config files aren't actually used here, but can be used by other +# programs. I would use them here, but bitcoind seems to have some issues +# reading from the file when using regtest. + +cat > "${ALPHA_DIR}/zcash.conf" < "${BETA_DIR}/zcash.conf" < "./alpha" < "./mine-alpha" < "./beta" < "./mine-beta" < "./reorg" < "./new-wallet" < "${HARNESS_DIR}/quit" < 0 { + in += uint64(tx.ValueBalance) + } else if tx.ValueBalance < 0 { + out += uint64(-1 * tx.ValueBalance) + } + return +} diff --git a/server/asset/zec/live_test.go b/server/asset/zec/live_test.go new file mode 100644 index 0000000000..1f782c0891 --- /dev/null +++ b/server/asset/zec/live_test.go @@ -0,0 +1,81 @@ +//go:build zeclive + +// go test -v -tags zeclive -run UTXOStats +// ----------------------------------- +// Grab the most recent block and iterate it's outputs, taking account of +// how many UTXOs are found, how many are of an unknown type, etc. +// +// go test -v -tags zeclive -run P2SHStats +// ----------------------------------------- +// For each output in the last block, check it's previous outpoint to see if +// it's a P2SH or P2WSH. If so, takes statistics on the script types, including +// for the redeem script. +// +// go test -v -tags zeclive -run LiveFees +// ------------------------------------------ +// Test that fees rates are parsed without error and that a few historical fee +// rates are correct + +package zec + +import ( + "context" + "fmt" + "os" + "testing" + + "decred.org/dcrdex/dex" + "decred.org/dcrdex/server/asset/btc" +) + +var ( + zec *ZECBackend + ctx context.Context +) + +func TestMain(m *testing.M) { + // Wrap everything for defers. + doIt := func() int { + logger := dex.StdOutLogger("ZECTEST", dex.LevelTrace) + be, err := NewBackend("", logger, dex.Mainnet) + if err != nil { + fmt.Printf("NewBackend error: %v\n", err) + return 1 + } + + var ok bool + zec, ok = be.(*ZECBackend) + if !ok { + fmt.Printf("Could not cast asset.Backend to *Backend") + return 1 + } + + ctx, cancel := context.WithCancel(context.Background()) + wg, err := zec.Connect(ctx) + if err != nil { + fmt.Printf("Connect failed: %v", err) + return 1 + } + defer wg.Wait() + defer cancel() + + return m.Run() + } + + os.Exit(doIt()) +} + +func TestUTXOStats(t *testing.T) { + btc.LiveUTXOStats(zec.Backend, t) +} + +func TestP2SHStats(t *testing.T) { + btc.LiveP2SHStats(zec.Backend, t, 50) +} + +func TestLiveFees(t *testing.T) { + btc.LiveFeeRates(zec.Backend, t, map[string]uint64{ + "920456117a0a9c55867e55cb02487d20a39feb4e4f6c9a69ec6f55fb243123e7": 5, + "90c2fbb3636e5bd8d35fc17202bfe86935fad3e8244e736f9b267c8d0ad14f90": 4631, + }) +} diff --git a/server/asset/zec/zec.go b/server/asset/zec/zec.go new file mode 100644 index 0000000000..c1c7cd0d65 --- /dev/null +++ b/server/asset/zec/zec.go @@ -0,0 +1,146 @@ +// This code is available on the terms of the project LICENSE.md file, +// also available online at https://blueoakcouncil.org/license/1.0.0. + +package zec + +import ( + "fmt" + "math" + + "decred.org/dcrdex/dex" + dexbtc "decred.org/dcrdex/dex/networks/btc" + dexzec "decred.org/dcrdex/dex/networks/zec" + "decred.org/dcrdex/server/asset" + "decred.org/dcrdex/server/asset/btc" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcutil" + dcrchaincfg "github.com/decred/dcrd/chaincfg/v3" +) + +// Driver implements asset.Driver. +type Driver struct{} + +// Setup creates the ZCash backend. Start the backend with its Run method. +func (d *Driver) Setup(configPath string, logger dex.Logger, network dex.Network) (asset.Backend, error) { + return NewBackend(configPath, logger, network) +} + +// DecodeCoinID creates a human-readable representation of a coin ID for +// ZCash. +func (d *Driver) DecodeCoinID(coinID []byte) (string, error) { + // ZCash and Bitcoin have the same tx hash and output format. + return (&btc.Driver{}).DecodeCoinID(coinID) +} + +// Version returns the Backend implementation's version number. +func (d *Driver) Version() uint32 { + return version +} + +// UnitInfo returns the dex.UnitInfo for the asset. +func (d *Driver) UnitInfo() dex.UnitInfo { + return dexzec.UnitInfo +} + +func init() { + asset.Register(BipID, &Driver{}) +} + +const ( + version = 0 + BipID = 133 + assetName = "zec" + feeConfs = 10 // Block time is 75 seconds +) + +// NewBackend generates the network parameters and creates a zec backend as a +// btc clone using an asset/btc helper function. +func NewBackend(configPath string, logger dex.Logger, network dex.Network) (asset.Backend, error) { + var btcParams *chaincfg.Params + var addrParams *dcrchaincfg.Params + switch network { + case dex.Mainnet: + btcParams = dexzec.MainNetParams + addrParams = dexzec.MainNetAddressParams + case dex.Testnet: + btcParams = dexzec.TestNet4Params + addrParams = dexzec.TestNet4AddressParams + case dex.Regtest: + btcParams = dexzec.RegressionNetParams + addrParams = dexzec.RegressionNetAddressParams + default: + return nil, fmt.Errorf("unknown network ID %v", network) + } + + // Designate the clone ports. These will be overwritten by any explicit + // settings in the configuration file. + ports := dexbtc.NetPorts{ + Mainnet: "8232", + Testnet: "18232", + Simnet: "18232", + } + + if configPath == "" { + configPath = dexbtc.SystemConfigPath("zcash") + } + + be, err := btc.NewBTCClone(&btc.BackendCloneConfig{ + Name: assetName, + Segwit: false, + ConfigPath: configPath, + Logger: logger, + Net: network, + ChainParams: btcParams, + Ports: ports, + AddressDecoder: func(addr string, net *chaincfg.Params) (btcutil.Address, error) { + return dexzec.DecodeAddress(addr, addrParams, btcParams) + }, + FeeEstimator: func(cl *btc.RPCClient) (uint64, error) { + // ZCash estimateFee + var r float64 + if err := cl.Call("estimatefee", []interface{}{feeConfs}, &r); err != nil { + return 0, err + } + if r <= 0 { + return 0, fmt.Errorf("fee could not be estimated") + } + return uint64(math.Round(r * 1e8)), nil + }, + InitTxSize: dexzec.InitTxSize, + InitTxSizeBase: dexzec.InitTxSizeBase, + NumericGetRawRPC: true, + }) + if err != nil { + return nil, err + } + + return &ZECBackend{ + Backend: be, + addrParams: addrParams, + btcParams: btcParams, + }, nil +} + +// ZECBackend embeds *btc.Backend and re-implements the Contract method to deal +// with ZCash address translation. +type ZECBackend struct { + *btc.Backend + btcParams *chaincfg.Params + addrParams *dcrchaincfg.Params +} + +// Contract returns the output from embedded Backend's Contract method, but +// with the SwapAddress field converted to ZCash encoding. +// TODO: Drop this in favor of an AddressEncoder field in the +// BackendCloneConfig. +func (be *ZECBackend) Contract(coinID []byte, redeemScript []byte) (*asset.Contract, error) { // Contract.SwapAddress + contract, err := be.Backend.Contract(coinID, redeemScript) + if err != nil { + return nil, err + } + contract.SwapAddress, err = dexzec.RecodeAddress(contract.SwapAddress, be.addrParams, be.btcParams) + if err != nil { + return nil, err + } + return contract, nil +} diff --git a/server/asset/zec/zec_test.go b/server/asset/zec/zec_test.go new file mode 100644 index 0000000000..501195555f --- /dev/null +++ b/server/asset/zec/zec_test.go @@ -0,0 +1,41 @@ +//go:build !zeclive + +package zec + +import ( + "encoding/hex" + "testing" + + dexzec "decred.org/dcrdex/dex/networks/zec" + "decred.org/dcrdex/server/asset/btc" +) + +func TestCompatibility(t *testing.T) { + fromHex := func(str string) []byte { + b, err := hex.DecodeString(str) + if err != nil { + t.Fatalf("error decoding %s: %v", str, err) + } + return b + } + + pkhAddr := "t1SqYLhzHyGoWwatRNGrTt4ueqivKdJpFY4" + btcPkhAddr, err := dexzec.DecodeAddress(pkhAddr, dexzec.MainNetAddressParams, dexzec.MainNetParams) + if err != nil { + t.Fatalf("error decoding p2pkh address: %v", err) + } + + shAddr := "t3ZJCdehVh9MTm6BaKWZmWy5Hsw7PhJxmTc" + btcShAddr, err := dexzec.DecodeAddress(shAddr, dexzec.MainNetAddressParams, dexzec.MainNetParams) + if err != nil { + t.Fatalf("error decoding p2sh address: %v", err) + } + + items := &btc.CompatibilityItems{ + P2PKHScript: fromHex("76a91462553d6a85afe7753cbe8dc57c7f34f6a8efd79f88ac"), + PKHAddr: btcPkhAddr.String(), + P2SHScript: fromHex("a914a19f5d7d23bbbff0695363f932c8d67c0169963f87"), + SHAddr: btcShAddr.String(), + } + btc.CompatibilityCheck(items, dexzec.MainNetParams, t) +} diff --git a/server/cmd/dcrdex/main.go b/server/cmd/dcrdex/main.go index 237549f540..5966a73fb9 100644 --- a/server/cmd/dcrdex/main.go +++ b/server/cmd/dcrdex/main.go @@ -23,6 +23,7 @@ import ( _ "decred.org/dcrdex/server/asset/dcr" // register dcr asset _ "decred.org/dcrdex/server/asset/doge" // register doge asset _ "decred.org/dcrdex/server/asset/ltc" // register ltc asset + _ "decred.org/dcrdex/server/asset/zec" // register zec asset dexsrv "decred.org/dcrdex/server/dex" "github.com/decred/dcrd/dcrec/secp256k1/v4" ) diff --git a/server/cmd/dexcoin/main.go b/server/cmd/dexcoin/main.go index 9df496a24f..5f29b0f163 100644 --- a/server/cmd/dexcoin/main.go +++ b/server/cmd/dexcoin/main.go @@ -16,6 +16,7 @@ import ( _ "decred.org/dcrdex/server/asset/dcr" _ "decred.org/dcrdex/server/asset/doge" _ "decred.org/dcrdex/server/asset/ltc" + _ "decred.org/dcrdex/server/asset/zec" ) type coinDecoder func([]byte) (string, error)