diff --git a/README.md b/README.md new file mode 100644 index 0000000..0d8e47b --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# ZBXCAT +A work-in-progress for Zcash Bitcoin Cross-Chain Atomic Transactions + +Contains basic scripts we're still testing in regtest mode on both networks. This may all be refactored as we go. + +The ZBXCAT directory contains BitcoinRPC and ZcashRPC, wrappers around the rpc interface that can be imported as Python modules. + +The settings.py file in BitcoinRPC and ZcashRPC parse the config files for username/password and set the network ports. + +Most functions are named exactly the same as the rpc methods, except for a few additional custom functions that do things like return only the version number. + +**EDIT**: The scripts now use the rpc proxy code in python-bitcoinlib, and ZDaemon's functions will be refactored into python-zcashlib (a Zcash fork of python-bitcoinlib) + +## Current status of scripts + +Run `redeem-preimage-p2sh.py` to test. It creates and redeems a p2sh transaction using a preimage. To successfully run it, you need python3, the dependencies installed, and a bitcoin daemon running in regtest mode. + +(Currently only tested on Bitcoin. Need to verify that the Zcash fork of python-bitcoinlib, one of the dependencies, works properly, then figure out the best way to install it.) + +`bitcoin-swap.py` contains all the functions that use a proxy to interact with a Bitcoin daemon. + +Use python3 to test. To create a virtualenv for python3, run this command from the top level of the directory: +``` +virtualenv -p python3 venv +source venv/bin/activate +``` + +Install dependencies for ZBXCAT: `pip install -r requirements.txt` + +## Installing python-zcashlib for testing and editing + +The Zcash fork of python-bitcoinlib that is currently in progress: + +`git clone https://github.com/arcalinea/python-bitcoinlib/tree/zcashlib` + +You can install this module locally through pip, in editable mode, so that changes you make are applied immediately. For install from local filesystem path: + +`pip install --editable (-e) ` + + +## Misc + +`protocol-pseudocode.py` is guaranteed to not run. It was written as a brainstorm/sketch of a hypothetical xcat protocol using @ebfull's fork of Zcash/Bitcoin that supports createhtlc as an rpc command. Including here in case it's useful in any way. + +I used the module [future](http://python-future.org/futurize.html) to make existing python2 code for the rpc interface compatible with python3. diff --git a/ZBXCAT/BitcoinRPC/BDaemon.py b/ZBXCAT/BitcoinRPC/BDaemon.py deleted file mode 100644 index 97f1f66..0000000 --- a/ZBXCAT/BitcoinRPC/BDaemon.py +++ /dev/null @@ -1,103 +0,0 @@ -from __future__ import print_function -from __future__ import absolute_import -import requests -import json - -from .settings import * - -class BDaemon(object): - - id_count = 0 - - def __init__(self, network=NETWORK, user=RPCUSER, password=RPCPASSWORD, timeout=TIMEOUT): - #TODO: check utf safety - url = network_url(network) - print('In mode:', network) - self.network = url - self.user = user.encode('utf8') - self.password = password.encode('utf8') - self.timeout = timeout - - - def _call(self, method, *args): - jsondata = json.dumps({ 'version': '2', - 'method': method, - 'params': args, - 'id': self.id_count}) - - r = requests.post(self.network, auth=(self.user,self.password), data=jsondata, timeout=self.timeout) - - self.id_count += 1 - - resp = json.loads(r.text) - - #TODO: deal with errors better. - error = resp['error'] - if error: - print(error) - - return resp['result'] - - # REGTEST - def generate(self, blocknum): - return self._call('generate', blocknum) - - def importaddress(self, script): - return self._call('importaddress', script) - - #Block Info - def getBlockHash(self, blockheight): - return self._call('getblockhash', blockheight) - - def getBlockByHash(self, blockhash): - return self._call('getblock', blockhash) - - def getBlockByHeight(self, blockheight): - return self.getBlockByHash(self.getBlockHash(blockheight)) - - # Custom methods to get Network Info - def getNetworkHeight(self): - return self._call('getblockcount') - - def getNetworkDifficulty(self): - return self._call('getdifficulty') - - def getVersion(self): - info = self._call('getnetworkinfo') - client = info['subversion'] - version = client.strip('/').split(':')[1] - return version - - def getConnectionCount(self): - return self._call('getconnectioncount') - - # Wallet Info - def getbalance(self): - return self._call('getbalance') - - def listunspent(self, minconf=1): - return self._call('listunspent', minconf) - - #Raw Txs - def gettransaction(self, txid): - return self._call('gettransaction', txid) - - def getrawtransaction(self, txid, verbose=0): - # default verbose=0 returns serialized, hex-encoded data - # verbose=1, returns a JSON obj of tx - return self._call('getrawtransaction', txid, verbose) - - def decoderawtransaction(self, txhex): - return self._call('decoderawtransaction', txhex) - - def sendrawtransaction(self, txhex): - return self._call('sendrawtransaction', txhex) - - def getnewaddress(self): - return self._call('getnewaddress') - - def sendtoaddress(self, taddress, amount): - return self._call('sendtoaddress', taddress, amount) - - def listunspent(self): - return self._call('listunspent') diff --git a/ZBXCAT/BitcoinRPC/__init__.py b/ZBXCAT/BitcoinRPC/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/ZBXCAT/BitcoinRPC/settings.py b/ZBXCAT/BitcoinRPC/settings.py deleted file mode 100644 index 1c2868e..0000000 --- a/ZBXCAT/BitcoinRPC/settings.py +++ /dev/null @@ -1,28 +0,0 @@ -import os, re - -TIMEOUT = 100 -#Default fee to use on network for txs. -DEFAULT_FEE = 0.01 -# Default network is testnet -NETWORK = 'TESTNET' -def network_url(network): - if network == 'TESTNET': - return "http://localhost:18332" - if network == 'REGTEST': - return "http://localhost:18332" - if network == 'MAINNET': - return "http://localhost:8332" - -bitcoinconf = os.path.expanduser('~/.bitcoin/bitcoin.conf') -def read_config(filename): - f = open(filename) - for line in f: - if re.match('rpcuser', line): - user = line.strip('\n').split('=')[1] - if re.match('rpcpassword', line): - password = line.strip('\n').split('=')[1] - return (user, password) -config = read_config(bitcoinconf) -# from bitcoin.conf -RPCUSER = config[0] -RPCPASSWORD = config[1] diff --git a/ZBXCAT/README.md b/ZBXCAT/README.md deleted file mode 100644 index c47544f..0000000 --- a/ZBXCAT/README.md +++ /dev/null @@ -1 +0,0 @@ -Python wrapper for bitcoind and zcashd, created for ZBXCAT - Zcash Bitcoin Cross-Chain Atomic Transactions. diff --git a/ZBXCAT/ZcashRPC/ZDaemon.py b/ZBXCAT/ZcashRPC/ZDaemon.py deleted file mode 100644 index cbe9a28..0000000 --- a/ZBXCAT/ZcashRPC/ZDaemon.py +++ /dev/null @@ -1,160 +0,0 @@ -from __future__ import print_function -from __future__ import absolute_import -import requests -import json - -from .settings import * - -class ZDaemon(object): - - id_count = 0 - - def __init__(self, network=NETWORK, user=RPCUSER, password=RPCPASSWORD, timeout=TIMEOUT): - #TODO: check utf safety - url = network_url(network) - print('In mode:', network) - self.network = url - self.user = user.encode('utf8') - self.password = password.encode('utf8') - self.timeout = timeout - - - def _call(self, method, *args): - jsondata = json.dumps({ 'version': '2', - 'method': method, - 'params': args, - 'id': self.id_count}) - - print("jsondata", jsondata) - - r = requests.post(self.network, auth=(self.user,self.password), data=jsondata, timeout=self.timeout) - - self.id_count += 1 - - resp = json.loads(r.text) - - #TODO: deal with errors better. - error = resp['error'] - if error: - print(error) - - return resp['result'] - - # REGTEST - def generate(self, blocknum): - return self._call('generate', blocknum) - - def importaddress(self, script): - return self._call('importaddress', script) - - #Block Info - def getBlockHash(self, blockheight): - return self._call('getblockhash', blockheight) - - def getBlockByHash(self, blockhash): - return self._call('getblock', blockhash) - - def getBlockByHeight(self, blockheight): - return self.getBlockByHash(self.getBlockHash(blockheight)) - - # Custom methods to get Network Info - def getNetworkHeight(self): - return self._call('getblockcount') - - def getNetworkDifficulty(self): - return self._call('getdifficulty') - - def getVersion(self): - info = self._call('getnetworkinfo') - client = info['subversion'] - version = client.strip('/').split(':')[1] - return version - - def getConnectionCount(self): - return self._call('getconnectioncount') - - # Wallet Info (transparent) - def getbalance(self): - return self._call('getbalance') - - def listunspent(self, minconf=1): - return self._call('listunspent', minconf) - - #Raw Txs - def gettransaction(self, txid): - return self._call('gettransaction', txid) - - def getrawtransaction(self, txid, verbose=0): - # default verbose=0 returns serialized, hex-encoded data - # verbose=1, returns a JSON obj of tx - return self._call('getrawtransaction', txid, verbose) - - def decoderawtransaction(self, txhex): - return self._call('decoderawtransaction', txhex) - - def sendrawtransaction(self, txhex): - return self._call('sendrawtransaction', txhex) - - # taddr methods - def getnewaddress(self): - return self._call('getnewaddress') - - def sendtoaddress(self, taddress, amount): - return self._call('sendtoaddress', taddress, amount) - - def listunspent(self): - return self._call('listunspent') - - # Custom method to find a taddr with spendable utxos for z_sendmany - def find_taddr_with_unspent(self): - unspent = self._call('listunspent') - for utxo in unspent: - if utxo['spendable'] == True and utxo['amount'] > 0.1: - # Check that it's not a coinbase tx - tx = zd.gettransaction(utxo['txid']) - if 'generated' not in tx: - return tx['address'] - - def sweep_coinbase(self, zaddr): - cb = [] - utxos = self.listunspent() - for utxo in utxos: - tx = self.gettransaction(utxo['txid']) - if 'generated' in tx and tx['generated'] == True: - cb.append(utxo) - for coin in cb: - amount = coin['amount'] - 0.0001 - opid = self.z_sendmany(coin['address'], zaddr, amount) - print("OPID of z_sendmany: ", opid) - status = self.z_getoperationstatus(opid) - print("Status: ", status[0]['status']) - - # zaddr methods - def z_gettotalbalance(self): - return self._call('z_gettotalbalance') - - def z_getnewaddress(self): - return self._call('z_getnewaddress') - - def z_listaddresses(self): - return self._call('z_listaddresses') - - def z_listreceivedbyaddress(self, zaddr, minconf=1): - return self._call('z_listreceivedbyaddress', zaddr, minconf) - - def z_getoperationstatus(self, opid): - return self._call('z_getoperationstatus', ["{0}".format(opid)]) - - def z_getoperationresult(self, opid): - return self._call('z_getoperationresult', ["{0}".format(opid)]) - - # With addition of encrypted memo field - def z_sendmany(self, sender, receiver, amount=0.0001, memo=''): - amts_array = [] - if memo == '': - amounts = {"address": receiver, "amount": amount} - else: - memo = memo.encode('hex') - amounts = {"address": receiver, "amount": amount, "memo": memo} - amts_array.append(amounts) - return self._call('z_sendmany', sender, amts_array) diff --git a/ZBXCAT/ZcashRPC/ZDaemon.pyc b/ZBXCAT/ZcashRPC/ZDaemon.pyc deleted file mode 100644 index 0b074e6..0000000 Binary files a/ZBXCAT/ZcashRPC/ZDaemon.pyc and /dev/null differ diff --git a/ZBXCAT/ZcashRPC/__init__.py b/ZBXCAT/ZcashRPC/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/ZBXCAT/ZcashRPC/__init__.pyc b/ZBXCAT/ZcashRPC/__init__.pyc deleted file mode 100644 index e7684c8..0000000 Binary files a/ZBXCAT/ZcashRPC/__init__.pyc and /dev/null differ diff --git a/ZBXCAT/ZcashRPC/__pycache__/ZDaemon.cpython-35.pyc b/ZBXCAT/ZcashRPC/__pycache__/ZDaemon.cpython-35.pyc deleted file mode 100644 index 63efae8..0000000 Binary files a/ZBXCAT/ZcashRPC/__pycache__/ZDaemon.cpython-35.pyc and /dev/null differ diff --git a/ZBXCAT/ZcashRPC/__pycache__/__init__.cpython-35.pyc b/ZBXCAT/ZcashRPC/__pycache__/__init__.cpython-35.pyc deleted file mode 100644 index 4edf664..0000000 Binary files a/ZBXCAT/ZcashRPC/__pycache__/__init__.cpython-35.pyc and /dev/null differ diff --git a/ZBXCAT/ZcashRPC/__pycache__/settings.cpython-35.pyc b/ZBXCAT/ZcashRPC/__pycache__/settings.cpython-35.pyc deleted file mode 100644 index 7be1a02..0000000 Binary files a/ZBXCAT/ZcashRPC/__pycache__/settings.cpython-35.pyc and /dev/null differ diff --git a/ZBXCAT/ZcashRPC/settings.py b/ZBXCAT/ZcashRPC/settings.py deleted file mode 100644 index 1e5270a..0000000 --- a/ZBXCAT/ZcashRPC/settings.py +++ /dev/null @@ -1,38 +0,0 @@ -import os, re - -#Timeout needs to be high for any pour operations -TIMEOUT = 600 -#Default fee to use on network for txs. -DEFAULT_FEE = 0.01 - -# Default is testnet -NETWORK = 'TESTNET' -def network_url(network): - if network == 'TESTNET': - return "http://localhost:18232" - if network == 'REGTEST': - return "http://localhost:18232" - if network == 'MAINNET': - return "http://localhost:18444" - -zcashconf = os.path.expanduser('~/.zcash/zcash.conf') -def read_config(filename): - f = open(filename) - for line in f: - if re.match('rpcuser', line): - user = line.strip('\n').split('=')[1] - if re.match('rpcpassword', line): - password = line.strip('\n').split('=')[1] - return (user, password) -config = read_config(zcashconf) -# from zcash conf -RPCUSER = config[0] -RPCPASSWORD = config[1] - - -#TESTS -#for tests (sample data here - replace with your own) -TEST_TXID = '' -TEST_ZADDR = "" -TEST_TADDR = "" -TEST_ZSECRET = "" diff --git a/ZBXCAT/ZcashRPC/settings.pyc b/ZBXCAT/ZcashRPC/settings.pyc deleted file mode 100644 index b4a4c1d..0000000 Binary files a/ZBXCAT/ZcashRPC/settings.pyc and /dev/null differ diff --git a/ZBXCAT/__init__.py b/ZBXCAT/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/ZBXCAT/__init__.pyc b/ZBXCAT/__init__.pyc deleted file mode 100644 index 3e2ddb4..0000000 Binary files a/ZBXCAT/__init__.pyc and /dev/null differ diff --git a/ZBXCAT/__pycache__/__init__.cpython-35.pyc b/ZBXCAT/__pycache__/__init__.cpython-35.pyc deleted file mode 100644 index 6565f3f..0000000 Binary files a/ZBXCAT/__pycache__/__init__.cpython-35.pyc and /dev/null differ diff --git a/ZBXCAT/examples/__init__.py b/ZBXCAT/examples/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/ZBXCAT/requirements.txt b/ZBXCAT/requirements.txt deleted file mode 100644 index a8ffe71..0000000 --- a/ZBXCAT/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -requests -python-bitcoinlib diff --git a/ZBXCAT/tests.py b/ZBXCAT/tests.py deleted file mode 100644 index 3ae4048..0000000 --- a/ZBXCAT/tests.py +++ /dev/null @@ -1,38 +0,0 @@ -from __future__ import print_function -from __future__ import absolute_import -import os.path -import sys - -from .rpc.ZDaemon import * -from settings import * - -def test_daemon(): - zd = ZDaemon() - - # Network tests - print(zd.getBlockHash(100)) - print(zd.getBlockByHash(zd.getBlockHash(100))) - print(zd.getBlockByHeight(100)) - print(zd.getNetworkHeight()) - print(zd.getNetworkDifficulty()) - print(zd.getVersion()) - print(zd.getConnectionCount()) - - # Taddr Wallet tests - print(zd.getbalance()) - print(zd.listunspent()) - print(zd.getnewaddress()) - - # Zaddr wallet tests - print(zd.z_getnewaddress()) - zaddrs = zd.z_listaddresses() - print(zaddrs) - zaddr_received = zd.z_listreceivedbyaddress(zaddr) - print(zaddr_received) - - # TODO: test z_sendmany, and use regtest - -if __name__ == "__main__": - # test_daemon() - zd = ZDaemon() - print(zd.getVersion()) diff --git a/daemon.py b/daemon.py deleted file mode 100755 index 501e572..0000000 --- a/daemon.py +++ /dev/null @@ -1,35 +0,0 @@ -from ZBXCAT.BitcoinRPC.BDaemon import * - -bd = BDaemon('REGTEST') -# v = bd.getVersion() -# print(v) - -def generate(num): - gen = bd.generate(num) - print("Generated blocks", gen) - -def fund_p2sh(p2sh, amount): - fund_tx = bd.sendtoaddress(p2sh, amount) - return fund_tx - -def tx_details(txid): - tx = bd.gettransaction(txid) - details = tx['details'][0] - return details - -# These two methods are placeholders -def get_recipient_address(): - address = bd.getnewaddress() - return address - -def get_sender_address(): - address = bd.getnewaddress() - return address - -def importaddress(addr): - res = bd.importaddress(addr) - return res - -def sendrawtx(hex): - txid = bd.sendrawtransaction(hex) - return txid diff --git a/xcat_htlc_zec.py b/protocol-pseudocode.py similarity index 100% rename from xcat_htlc_zec.py rename to protocol-pseudocode.py diff --git a/redeem-preimage-p2sh.py b/redeem-preimage-p2sh.py index e8a8f8f..14f3b05 100755 --- a/redeem-preimage-p2sh.py +++ b/redeem-preimage-p2sh.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 # Based on spend-p2sh-txout.py from python-bitcoinlib. -# Copyright (C) 2014 The python-bitcoinlib developers # Copyright (C) 2017 The Zcash developers import sys @@ -9,6 +8,8 @@ if sys.version_info.major < 3: sys.stderr.write('Sorry, Python 3.x required by this example.\n') sys.exit(1) +import bitcoin +import bitcoin.rpc from bitcoin import SelectParams from bitcoin.core import b2x, lx, COIN, COutPoint, CMutableTxOut, CMutableTxIn, CMutableTransaction, Hash160 from bitcoin.core.script import CScript, OP_DUP, OP_IF, OP_ELSE, OP_ENDIF, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG, SignatureHash, SIGHASH_ALL @@ -17,23 +18,21 @@ from bitcoin.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH from bitcoin.wallet import CBitcoinAddress, CBitcoinSecret import hashlib -from daemon import * -SelectParams('testnet') +# SelectParams('testnet') +# To get transactions not in your wallet, must set -txindex=1 +SelectParams('regtest') +proxy = bitcoin.rpc.Proxy() # The parameters needed for the htlc - hash preimage, sender/seller address, recipient/buyer address, num of blocks for timeout preimage = b'preimage' h = hashlib.sha256(preimage).digest() -seckey = CBitcoinSecret('cShLodcaVA7JjDXnRaK5jsmQhPJmBDeXPd4Fzx8dRD9ih6gmXKH6') -# AUTOMATE - get recipient addr -recipient_address = get_recipient_address() -print(' recipient_address (newly generated)', recipient_address) -sender_address = get_sender_address() -print(' sender_address (newly generated)', sender_address) - -recipientpubkey = CBitcoinAddress('mheZcjatFMjcHX5hVQdAY4Lvxm7q7rXuU2') -senderpubkey = CBitcoinAddress('mheZcjatFMjcHX5hVQdAY4Lvxm7q7rXuU2') +# proxy.getnewaddress() returns CBitcoinAddress +recipientpubkey = proxy.getnewaddress() +senderpubkey = proxy.getnewaddress() +# privkey of the recipient, used to sign the redeemTx +seckey = proxy.dumpprivkey(recipientpubkey) blocknum = 7 # Create a htlc redeemScript. Similar to a scriptPubKey the redeemScript must be @@ -43,55 +42,36 @@ txin_redeemScript = CScript([OP_IF, OP_SHA256, h, OP_EQUALVERIFY,OP_DUP, OP_HASH senderpubkey, OP_ENDIF,OP_EQUALVERIFY, OP_CHECKSIG]) print("redeem script:", b2x(txin_redeemScript)) -#print("scriptpubey:", b2x(recipientpubkey.to_scriptPubKey())) - -# Create P2SH scriptPubKey from redeemScript. +# Create P2SH scriptPubKey from redeemScript. txin_scriptPubKey = txin_redeemScript.to_p2sh_scriptPubKey() print("p2sh_scriptPubKey", b2x(txin_scriptPubKey)) -# Convert the P2SH scriptPubKey to a base58 Bitcoin address and print it. -# You'll need to send some funds to it to create a txout to spend. +# Convert the P2SH scriptPubKey to a base58 Bitcoin address txin_p2sh_address = CBitcoinAddress.from_scriptPubKey(txin_scriptPubKey) -print('Pay to:',str(txin_p2sh_address)) - p2sh = str(txin_p2sh_address) +print('Pay to:', p2sh) -#AUTOMATE funding tx, seller sends funds -amount = 1.0 -fund_tx = fund_p2sh(p2sh, amount) -print("TXID", fund_tx) - -# AUTOMATE import this address? -importaddress(p2sh) -print('p2sh address imported') - -# MINE THE FUNDED TX -generate(1) +# AUTOMATE Send funds to p2sh +amount = 1.0*COIN +fund_tx = proxy.sendtoaddress(txin_p2sh_address, amount) print('Now redeeming.........') -# lx() takes *little-endian* hex and converts it to bytes; in Bitcoin -# transaction hashes are shown little-endian rather than the usual big-endian. - -# AUTOMATE -txid = lx(fund_tx) -details = tx_details(fund_tx) -print('get details of fund_tx', details) +# AUTOMATE getting vout of funding tx +txinfo = proxy.gettransaction(fund_tx) +details = txinfo['details'][0] vout = details['vout'] -print('vout', vout) -# Create the txin structure, which includes the outpoint. The scriptSig -# defaults to being empty. -txin = CMutableTxIn(COutPoint(txid, vout)) +# Create the txin structure. scriptSig defaults to being empty. +# The input is the p2sh funding transaction txid, vout is its index +txin = CMutableTxIn(COutPoint(fund_tx, vout)) -# Create the txout. This time we create the scriptPubKey from a Bitcoin -# address. -# AUTOMATE: amount and address set above -redeemed = amount*COIN -fee = 0.001*COIN -txout = CMutableTxOut(redeemed - fee, CBitcoinAddress(recipient_address).to_scriptPubKey()) +# Create the txout. Pays out to recipient, so uses recipient's pubkey +# Withdraw full amount minus fee +default_fee = 0.001*COIN +txout = CMutableTxOut(amount - default_fee, recipientpubkey.to_scriptPubKey()) -# Create the unsigned transaction. +# Create the unsigned raw transaction. tx = CMutableTransaction([txin], [txout]) # Calculate the signature hash for that transaction. Note how the script we use @@ -103,19 +83,13 @@ sighash = SignatureHash(txin_redeemScript, tx, 0, SIGHASH_ALL) sig = seckey.sign(sighash) + bytes([SIGHASH_ALL]) # Set the scriptSig of our transaction input appropriately. -txin.scriptSig = CScript([ sig,seckey.pub, preimage, OP_TRUE, txin_redeemScript]) +txin.scriptSig = CScript([ sig, seckey.pub, preimage, OP_TRUE, txin_redeemScript]) -print("scriptSig:", b2x(txin.scriptSig)) +print("Redeem tx hex:", b2x(tx.serialize())) # Verify the signature worked. VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,)) -# Tx to hex -hextx = b2x(tx.serialize()) -print(hextx) - -# AUTOMATE: Send raw redeem tx -txid = sendrawtx(hextx) -print("txid of submitted redeem tx", txid) - -generate(1) +print("Now sending redeem transaction.......") +txid = proxy.sendrawtransaction(tx) +print("Txid of submitted redeem tx: ", b2x(txid))