Merge branch 'master' of https://github.com/zcash-hackworks/zbxcat
This commit is contained in:
commit
772f1b857b
|
@ -10,16 +10,20 @@ import bitcoin.rpc
|
||||||
from bitcoin import SelectParams
|
from bitcoin import SelectParams
|
||||||
from bitcoin.core import b2x, lx, b2lx, x, COIN, COutPoint, CMutableTxOut, CMutableTxIn, CMutableTransaction, Hash160, CTransaction
|
from bitcoin.core import b2x, lx, b2lx, x, COIN, COutPoint, CMutableTxOut, CMutableTxIn, CMutableTransaction, Hash160, CTransaction
|
||||||
from bitcoin.base58 import decode
|
from bitcoin.base58 import decode
|
||||||
from bitcoin.core.script import CScript, OP_DUP, OP_IF, OP_ELSE, OP_ENDIF, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG, SignatureHash, SIGHASH_ALL, OP_FALSE, OP_DROP, OP_CHECKLOCKTIMEVERIFY, OP_SHA256, OP_TRUE
|
from bitcoin.core.script import CScript, OP_DUP, OP_IF, OP_ELSE, OP_ENDIF, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG, SignatureHash, SIGHASH_ALL, OP_FALSE, OP_DROP, OP_CHECKLOCKTIMEVERIFY, OP_SHA256, OP_TRUE, OP_FALSE
|
||||||
from bitcoin.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH
|
from bitcoin.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH
|
||||||
from bitcoin.wallet import CBitcoinAddress, CBitcoinSecret, P2SHBitcoinAddress, P2PKHBitcoinAddress
|
from bitcoin.wallet import CBitcoinAddress, CBitcoinSecret, P2SHBitcoinAddress, P2PKHBitcoinAddress
|
||||||
|
|
||||||
from xcat.utils import *
|
from xcat.utils import *
|
||||||
|
import logging
|
||||||
|
|
||||||
FEE = 0.001*COIN
|
FEE = 0.001*COIN
|
||||||
|
|
||||||
class bitcoinProxy():
|
class bitcoinProxy():
|
||||||
def __init__(self, network='regtest', timeout=900):
|
def __init__(self, network='regtest', timeout=900):
|
||||||
|
if network is not 'testnet' and network is not 'mainnet':
|
||||||
|
network='regtest'
|
||||||
|
logging.debug("NETWORK in proxy: {0}".format(network))
|
||||||
self.network = network
|
self.network = network
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
|
|
||||||
|
@ -143,7 +147,7 @@ class bitcoinProxy():
|
||||||
print("Parsing script for redeem_contract...")
|
print("Parsing script for redeem_contract...")
|
||||||
scriptarray = self.parse_script(contract.redeemScript)
|
scriptarray = self.parse_script(contract.redeemScript)
|
||||||
redeemblocknum = scriptarray[8]
|
redeemblocknum = scriptarray[8]
|
||||||
redeemPubKey = P2PKHBitcoinAddress.from_bytes(x(scriptarray[6]))
|
self.redeemPubKey = P2PKHBitcoinAddress.from_bytes(x(scriptarray[6]))
|
||||||
refundPubKey = P2PKHBitcoinAddress.from_bytes(x(scriptarray[13]))
|
refundPubKey = P2PKHBitcoinAddress.from_bytes(x(scriptarray[13]))
|
||||||
p2sh = contract.p2sh
|
p2sh = contract.p2sh
|
||||||
#checking there are funds in the address
|
#checking there are funds in the address
|
||||||
|
@ -161,21 +165,30 @@ class bitcoinProxy():
|
||||||
blockcount = self.bitcoind.getblockcount()
|
blockcount = self.bitcoind.getblockcount()
|
||||||
print("\nCurrent blocknum at time of redeem on Zcash:", blockcount)
|
print("\nCurrent blocknum at time of redeem on Zcash:", blockcount)
|
||||||
if blockcount < int(redeemblocknum):
|
if blockcount < int(redeemblocknum):
|
||||||
print('redeemPubKey', redeemPubKey)
|
return self.redeem(contract, fundtx, secret)
|
||||||
zec_redeemScript = CScript(x(contract.redeemScript))
|
else:
|
||||||
|
print("nLocktime exceeded, refunding")
|
||||||
|
return self.refund(contract)
|
||||||
|
else:
|
||||||
|
print("No contract for this p2sh found in database", p2sh)
|
||||||
|
|
||||||
|
def redeem(self, contract, fundtx, secret):
|
||||||
|
print('redeemPubKey', self.redeemPubKey)
|
||||||
|
# TODO: Compare with script on blockchain?
|
||||||
|
redeemScript = CScript(x(contract.redeemScript))
|
||||||
txin = CMutableTxIn(fundtx['outpoint'])
|
txin = CMutableTxIn(fundtx['outpoint'])
|
||||||
txout = CMutableTxOut(fundtx['amount'] - FEE, redeemPubKey.to_scriptPubKey())
|
txout = CMutableTxOut(fundtx['amount'] - FEE, self.redeemPubKey.to_scriptPubKey())
|
||||||
# Create the unsigned raw transaction.
|
# Create the unsigned raw transaction.
|
||||||
tx = CMutableTransaction([txin], [txout])
|
tx = CMutableTransaction([txin], [txout])
|
||||||
sighash = SignatureHash(zec_redeemScript, tx, 0, SIGHASH_ALL)
|
sighash = SignatureHash(redeemScript, tx, 0, SIGHASH_ALL)
|
||||||
# TODO: protect privkey better, separate signing from rawtx creation
|
# TODO: protect privkey better, separate signing from rawtx creation
|
||||||
privkey = self.bitcoind.dumpprivkey(redeemPubKey)
|
privkey = self.bitcoind.dumpprivkey(self.redeemPubKey)
|
||||||
sig = privkey.sign(sighash) + bytes([SIGHASH_ALL])
|
sig = privkey.sign(sighash) + bytes([SIGHASH_ALL])
|
||||||
preimage = secret.encode('utf-8')
|
preimage = secret.encode('utf-8')
|
||||||
txin.scriptSig = CScript([sig, privkey.pub, preimage, OP_TRUE, zec_redeemScript])
|
txin.scriptSig = CScript([sig, privkey.pub, preimage, OP_TRUE, redeemScript])
|
||||||
|
|
||||||
# print("txin.scriptSig", b2x(txin.scriptSig))
|
# print("txin.scriptSig", b2x(txin.scriptSig))
|
||||||
txin_scriptPubKey = zec_redeemScript.to_p2sh_scriptPubKey()
|
txin_scriptPubKey = redeemScript.to_p2sh_scriptPubKey()
|
||||||
print('Raw redeem transaction hex: ', b2x(tx.serialize()))
|
print('Raw redeem transaction hex: ', b2x(tx.serialize()))
|
||||||
VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,))
|
VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,))
|
||||||
print("Script verified, sending raw transaction...")
|
print("Script verified, sending raw transaction...")
|
||||||
|
@ -183,15 +196,32 @@ class bitcoinProxy():
|
||||||
fund_tx = str(fundtx['outpoint'])
|
fund_tx = str(fundtx['outpoint'])
|
||||||
redeem_tx = b2x(lx(b2x(txid)))
|
redeem_tx = b2x(lx(b2x(txid)))
|
||||||
return {"redeem_tx": redeem_tx, "fund_tx": fund_tx}
|
return {"redeem_tx": redeem_tx, "fund_tx": fund_tx}
|
||||||
else:
|
|
||||||
print("nLocktime exceeded, refunding")
|
def refund(self, contract):
|
||||||
print('refundPubKey', refundPubKey)
|
fundtx = self.find_transaction_to_address(contract.p2sh)
|
||||||
txid = self.bitcoind.sendtoaddress(refundPubKey, fundtx['amount'] - FEE)
|
print("Fund tx found in refund: ", fundtx)
|
||||||
fund_tx = str(fundtx['outpoint'])
|
refundPubKey = self.find_refundAddr(contract)
|
||||||
|
print('refundPubKey: {0}'.format(refundPubKey))
|
||||||
|
|
||||||
|
redeemScript = CScript(x(contract.redeemScript))
|
||||||
|
txin = CMutableTxIn(fundtx['outpoint'])
|
||||||
|
txout = CMutableTxOut(fundtx['amount'] - FEE, refundPubKey.to_scriptPubKey())
|
||||||
|
# Create the unsigned raw transaction.
|
||||||
|
tx = CMutableTransaction([txin], [txout])
|
||||||
|
sighash = SignatureHash(redeemScript, tx, 0, SIGHASH_ALL)
|
||||||
|
privkey = self.bitcoind.dumpprivkey(refundPubKey)
|
||||||
|
sig = privkey.sign(sighash) + bytes([SIGHASH_ALL])
|
||||||
|
# Sign without secret
|
||||||
|
txin.scriptSig = CScript([sig, privkey.pub, OP_FALSE, redeemScript])
|
||||||
|
# txin.nSequence = 2185
|
||||||
|
txin_scriptPubKey = redeemScript.to_p2sh_scriptPubKey()
|
||||||
|
print('Raw redeem transaction hex: {0}'.format(b2x(tx.serialize())))
|
||||||
|
res = VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,))
|
||||||
|
print("Script verified, sending raw transaction... (NOT)", res)
|
||||||
|
txid = self.bitcoind.sendrawtransaction(tx)
|
||||||
refund_tx = b2x(lx(b2x(txid)))
|
refund_tx = b2x(lx(b2x(txid)))
|
||||||
|
fund_tx = str(fundtx['outpoint'])
|
||||||
return {"refund_tx": refund_tx, "fund_tx": fund_tx}
|
return {"refund_tx": refund_tx, "fund_tx": fund_tx}
|
||||||
else:
|
|
||||||
print("No contract for this p2sh found in database", p2sh)
|
|
||||||
|
|
||||||
def parse_script(self, script_hex):
|
def parse_script(self, script_hex):
|
||||||
redeemScript = self.bitcoind.call('decodescript', script_hex)
|
redeemScript = self.bitcoind.call('decodescript', script_hex)
|
||||||
|
@ -210,7 +240,7 @@ class bitcoinProxy():
|
||||||
return redeemAddr
|
return redeemAddr
|
||||||
|
|
||||||
def find_refundAddr(self, contract):
|
def find_refundAddr(self, contract):
|
||||||
scriptarray = parse_script(contract.redeemScript)
|
scriptarray = self.parse_script(contract.redeemScript)
|
||||||
funder = scriptarray[13]
|
funder = scriptarray[13]
|
||||||
refundAddr = P2PKHBitcoinAddress.from_bytes(x(funder))
|
refundAddr = P2PKHBitcoinAddress.from_bytes(x(funder))
|
||||||
return refundAddr
|
return refundAddr
|
||||||
|
@ -220,7 +250,7 @@ class bitcoinProxy():
|
||||||
txs = self.bitcoind.listunspent()
|
txs = self.bitcoind.listunspent()
|
||||||
for tx in txs:
|
for tx in txs:
|
||||||
if tx['address'] == CBitcoinAddress(p2sh):
|
if tx['address'] == CBitcoinAddress(p2sh):
|
||||||
print("Found tx to p2sh", p2sh)
|
logging.debug("Found tx to p2sh: {0}".format(p2sh))
|
||||||
return tx
|
return tx
|
||||||
|
|
||||||
def new_bitcoin_addr(self):
|
def new_bitcoin_addr(self):
|
||||||
|
|
32
xcat/cli.py
32
xcat/cli.py
|
@ -27,11 +27,12 @@ def checkSellStatus(tradeid):
|
||||||
if 'redeem_tx' in txs:
|
if 'redeem_tx' in txs:
|
||||||
trade.buy.redeem_tx = txs['redeem_tx']
|
trade.buy.redeem_tx = txs['redeem_tx']
|
||||||
print("Redeem tx: ", txs['redeem_tx'])
|
print("Redeem tx: ", txs['redeem_tx'])
|
||||||
elif 'refund_tx' in txs:
|
if 'refund_tx' in txs:
|
||||||
trade.buy.redeem_tx = txs['refund_tx']
|
trade.buy.redeem_tx = txs['refund_tx']
|
||||||
print("Refund tx: ", txs['refund_tx'])
|
print("Buyer refund tx: ", txs['refund_tx'])
|
||||||
|
txs = refund_contract(trade.sell) # Refund to seller
|
||||||
|
print("Your refund txid: ", txs['refund_tx'])
|
||||||
save_state(trade, tradeid)
|
save_state(trade, tradeid)
|
||||||
# Remove from db? Or just from temporary file storage
|
|
||||||
cleanup(tradeid)
|
cleanup(tradeid)
|
||||||
elif status == 'sellerFunded':
|
elif status == 'sellerFunded':
|
||||||
print("Buyer has not yet funded the contract where you offered to buy {0}, please wait for them to complete their part.".format(trade.buy.currency))
|
print("Buyer has not yet funded the contract where you offered to buy {0}, please wait for them to complete their part.".format(trade.buy.currency))
|
||||||
|
@ -101,6 +102,7 @@ def checkBuyStatus(tradeid):
|
||||||
save_state(trade, tradeid)
|
save_state(trade, tradeid)
|
||||||
print("XCAT trade complete!")
|
print("XCAT trade complete!")
|
||||||
else:
|
else:
|
||||||
|
# Search if tx has been refunded from p2sh
|
||||||
print("Secret not found in redeemtx")
|
print("Secret not found in redeemtx")
|
||||||
|
|
||||||
# Import a trade in hex, and save to db
|
# Import a trade in hex, and save to db
|
||||||
|
@ -173,9 +175,9 @@ def checktrade(tradeid):
|
||||||
def newtrade(tradeid, **kwargs):
|
def newtrade(tradeid, **kwargs):
|
||||||
print("Creating new XCAT trade...")
|
print("Creating new XCAT trade...")
|
||||||
erase_trade()
|
erase_trade()
|
||||||
tradeid, trade= initialize_trade(tradeid, conf=kwargs['conf'])
|
tradeid, trade= initialize_trade(tradeid, conf=kwargs['conf'], network=kwargs['network'])
|
||||||
print("Trade", trade)
|
print("New trade created: {0}".format(trade))
|
||||||
trade = seller_init(tradeid, trade)
|
trade = seller_init(tradeid, trade, network=kwargs['network'])
|
||||||
print("\nUse 'xcat exporttrade [tradeid]' to export the trade and sent to the buyer.\n")
|
print("\nUse 'xcat exporttrade [tradeid]' to export the trade and sent to the buyer.\n")
|
||||||
save_state(trade, tradeid)
|
save_state(trade, tradeid)
|
||||||
return trade
|
return trade
|
||||||
|
@ -199,12 +201,24 @@ def main():
|
||||||
'''))
|
'''))
|
||||||
parser.add_argument("command", action="store", help="list commands")
|
parser.add_argument("command", action="store", help="list commands")
|
||||||
parser.add_argument("arguments", action="store", nargs="*", help="add arguments")
|
parser.add_argument("arguments", action="store", nargs="*", help="add arguments")
|
||||||
|
parser.add_argument("-d", "--debug", action="store_true", help="Enable debug mode. Defaults to false")
|
||||||
parser.add_argument("-w", "--wormhole", action="store_true", help="Transfer trade data through magic-wormhole")
|
parser.add_argument("-w", "--wormhole", action="store_true", help="Transfer trade data through magic-wormhole")
|
||||||
parser.add_argument("-c", "--conf", action="store", help="Use default trade data in conf file.")
|
parser.add_argument("-c", "--conf", action="store", help="Use default trade data in conf file.")
|
||||||
parser.add_argument("-n", "--network", action="store", help="Set network to regtest or mainnet. Defaults to testnet while in alpha.")
|
parser.add_argument("-n", "--network", action="store", help="Set network to regtest or mainnet. Defaults to testnet while in alpha.")
|
||||||
# parser.add_argument("--daemon", "-d", action="store_true", help="Run as daemon process")
|
# parser.add_argument("--daemon", "-d", action="store_true", help="Run as daemon process")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.debug:
|
||||||
|
numeric_level = getattr(logging, 'DEBUG', None)
|
||||||
|
logging.basicConfig(format='%(levelname)s: %(message)s', level=numeric_level)
|
||||||
|
else:
|
||||||
|
logging.basicConfig(format='%(levelname)s: %(message)s', level='INFO')
|
||||||
|
|
||||||
|
if args.network:
|
||||||
|
NETWORK = args.network
|
||||||
|
else:
|
||||||
|
NETWORK = 'testnet'
|
||||||
|
|
||||||
command = args.command
|
command = args.command
|
||||||
if command == 'importtrade':
|
if command == 'importtrade':
|
||||||
if args.wormhole:
|
if args.wormhole:
|
||||||
|
@ -234,9 +248,9 @@ def main():
|
||||||
if len(args.arguments) < 1: throw("Usage: newtrade [tradeid]")
|
if len(args.arguments) < 1: throw("Usage: newtrade [tradeid]")
|
||||||
tradeid = args.arguments[0]
|
tradeid = args.arguments[0]
|
||||||
if args.conf == None:
|
if args.conf == None:
|
||||||
newtrade(tradeid, network=args.network, conf='cli')
|
newtrade(tradeid, network=NETWORK, conf='cli')
|
||||||
else:
|
else:
|
||||||
newtrade(tradeid, network=args.network, conf=args.conf)
|
newtrade(tradeid, network=NETWORK, conf=args.conf)
|
||||||
elif command == "daemon":
|
elif command == "daemon":
|
||||||
#TODO: not implemented
|
#TODO: not implemented
|
||||||
print("Run as daemon process")
|
print("Run as daemon process")
|
||||||
|
@ -248,8 +262,10 @@ def main():
|
||||||
tradeid = args.arguments[0]
|
tradeid = args.arguments[0]
|
||||||
checkBuyStatus(tradeid)
|
checkBuyStatus(tradeid)
|
||||||
elif command == "step3":
|
elif command == "step3":
|
||||||
|
generate(31)
|
||||||
tradeid = args.arguments[0]
|
tradeid = args.arguments[0]
|
||||||
checkSellStatus(tradeid)
|
checkSellStatus(tradeid)
|
||||||
elif command == "step4":
|
elif command == "step4":
|
||||||
|
# generate(1)
|
||||||
tradeid = args.arguments[0]
|
tradeid = args.arguments[0]
|
||||||
checkBuyStatus(tradeid)
|
checkBuyStatus(tradeid)
|
||||||
|
|
|
@ -8,24 +8,35 @@ import xcat.db as db
|
||||||
from xcat.xcatconf import *
|
from xcat.xcatconf import *
|
||||||
from xcat.bitcoinRPC import bitcoinProxy
|
from xcat.bitcoinRPC import bitcoinProxy
|
||||||
from xcat.zcashRPC import zcashProxy
|
from xcat.zcashRPC import zcashProxy
|
||||||
|
import logging
|
||||||
|
|
||||||
bitcoinRPC = bitcoinProxy()
|
bitcoinRPC = bitcoinProxy()
|
||||||
zcashRPC = zcashProxy()
|
zcashRPC = zcashProxy()
|
||||||
|
|
||||||
|
def generate(num):
|
||||||
|
bitcoinRPC.generate(num)
|
||||||
|
zcashRPC.generate(num)
|
||||||
|
|
||||||
def is_myaddr(address):
|
def is_myaddr(address):
|
||||||
|
# Handle different network prefixes
|
||||||
if address[:1] == 'm':
|
if address[:1] == 'm':
|
||||||
status = bitcoinRPC.validateaddress(address)
|
status = bitcoinRPC.validateaddress(address)
|
||||||
else:
|
else:
|
||||||
status = zcashRPC.validateaddress(address)
|
status = zcashRPC.validateaddress(address)
|
||||||
|
logging.debug("Address status: ", status)
|
||||||
|
if status['isvalid'] is False:
|
||||||
|
raise ValueError("Invalid address: %s" % address)
|
||||||
|
elif 'ismine' in status:
|
||||||
status = status['ismine']
|
status = status['ismine']
|
||||||
# print("Address {0} is mine: {1}".format(address, status))
|
|
||||||
return status
|
return status
|
||||||
|
|
||||||
def find_secret_from_fundtx(currency, p2sh, fundtx):
|
def find_secret_from_fundtx(currency, p2sh, fundtx):
|
||||||
if currency == 'bitcoin':
|
if currency == 'bitcoin':
|
||||||
secret = bitcoinRPC.find_secret(p2sh, fundtx)
|
secret = bitcoinRPC.find_secret(p2sh, fundtx)
|
||||||
else:
|
elif currency == 'zcash':
|
||||||
secret = zcashRPC.find_secret(p2sh, fundtx)
|
secret = zcashRPC.find_secret(p2sh, fundtx)
|
||||||
|
else:
|
||||||
|
raise ValueError("Currency not recognized: ", currency)
|
||||||
return secret
|
return secret
|
||||||
|
|
||||||
def import_addrs(trade):
|
def import_addrs(trade):
|
||||||
|
@ -36,17 +47,21 @@ def check_p2sh(currency, address):
|
||||||
if currency == 'bitcoin':
|
if currency == 'bitcoin':
|
||||||
print("Checking funds in Bitcoin p2sh")
|
print("Checking funds in Bitcoin p2sh")
|
||||||
return bitcoinRPC.check_funds(address)
|
return bitcoinRPC.check_funds(address)
|
||||||
else:
|
elif currency == 'zcash':
|
||||||
print("Checking funds in Zcash p2sh")
|
print("Checking funds in Zcash p2sh")
|
||||||
return zcashRPC.check_funds(address)
|
return zcashRPC.check_funds(address)
|
||||||
|
else:
|
||||||
|
raise ValueError("Currency not recognized: ", currency)
|
||||||
|
|
||||||
def check_fund_status(currency, address):
|
def check_fund_status(currency, address):
|
||||||
if currency == 'bitcoin':
|
if currency == 'bitcoin':
|
||||||
print("Checking funds in Bitcoin p2sh")
|
print("Checking funds in Bitcoin p2sh")
|
||||||
return bitcoinRPC.get_fund_status(address)
|
return bitcoinRPC.get_fund_status(address)
|
||||||
else:
|
elif currency == 'zcash':
|
||||||
print("Checking funds in Zcash p2sh")
|
print("Checking funds in Zcash p2sh")
|
||||||
return zcashRPC.get_fund_status(address)
|
return zcashRPC.get_fund_status(address)
|
||||||
|
else:
|
||||||
|
raise ValueError("Currency not recognized: ", currency)
|
||||||
|
|
||||||
# TODO: function to calculate appropriate locktimes between chains
|
# TODO: function to calculate appropriate locktimes between chains
|
||||||
# def verify_p2sh(trade):
|
# def verify_p2sh(trade):
|
||||||
|
@ -61,17 +76,50 @@ def check_fund_status(currency, address):
|
||||||
def create_htlc(currency, funder, redeemer, commitment, locktime):
|
def create_htlc(currency, funder, redeemer, commitment, locktime):
|
||||||
if currency == 'bitcoin':
|
if currency == 'bitcoin':
|
||||||
sell_p2sh = bitcoinRPC.hashtimelockcontract(funder, redeemer, commitment, locktime)
|
sell_p2sh = bitcoinRPC.hashtimelockcontract(funder, redeemer, commitment, locktime)
|
||||||
else:
|
elif currency == 'zcash':
|
||||||
sell_p2sh = zcashRPC.hashtimelockcontract(funder, redeemer, commitment, locktime)
|
sell_p2sh = zcashRPC.hashtimelockcontract(funder, redeemer, commitment, locktime)
|
||||||
|
else:
|
||||||
|
raise ValueError("Currency not recognized: ", currency)
|
||||||
return sell_p2sh
|
return sell_p2sh
|
||||||
|
|
||||||
def fund_htlc(currency, p2sh, amount):
|
def fund_htlc(currency, p2sh, amount):
|
||||||
if currency == 'bitcoin':
|
if currency == 'bitcoin':
|
||||||
txid = bitcoinRPC.fund_htlc(p2sh, amount)
|
txid = bitcoinRPC.fund_htlc(p2sh, amount)
|
||||||
else:
|
elif currency == 'zcash':
|
||||||
txid = zcashRPC.fund_htlc(p2sh, amount)
|
txid = zcashRPC.fund_htlc(p2sh, amount)
|
||||||
|
else:
|
||||||
|
raise ValueError("Currency not recognized: ", currency)
|
||||||
return txid
|
return txid
|
||||||
|
|
||||||
|
def redeem_p2sh(contract, secret):
|
||||||
|
currency = contract.currency
|
||||||
|
if currency == 'bitcoin':
|
||||||
|
res = bitcoinRPC.redeem_contract(contract, secret)
|
||||||
|
elif currency == 'zcash':
|
||||||
|
res = zcashRPC.redeem_contract(contract, secret)
|
||||||
|
else:
|
||||||
|
raise ValueError("Currency not recognized: ", currency)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def refund_contract(contract):
|
||||||
|
currency = contract.currency
|
||||||
|
if currency == 'bitcoin':
|
||||||
|
res = bitcoinRPC.refund(contract)
|
||||||
|
elif currency == 'zcash':
|
||||||
|
res = zcashRPC.refund(contract)
|
||||||
|
else:
|
||||||
|
raise ValueError("Currency not recognized: ", currency)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def parse_secret(currency, txid):
|
||||||
|
if currency == 'bitcoin':
|
||||||
|
secret = bitcoinRPC.parse_secret(txid)
|
||||||
|
elif currency == 'zcash':
|
||||||
|
secret = zcashRPC.parse_secret(txid)
|
||||||
|
else:
|
||||||
|
raise ValueError("Currency not recognized: ", currency)
|
||||||
|
return secret
|
||||||
|
|
||||||
def fund_contract(contract):
|
def fund_contract(contract):
|
||||||
txid = fund_htlc(contract.currency, contract.p2sh, contract.amount)
|
txid = fund_htlc(contract.currency, contract.p2sh, contract.amount)
|
||||||
return txid
|
return txid
|
||||||
|
@ -109,21 +157,6 @@ def create_buy_p2sh(trade, commitment, locktime):
|
||||||
|
|
||||||
save(trade)
|
save(trade)
|
||||||
|
|
||||||
def redeem_p2sh(contract, secret):
|
|
||||||
currency = contract.currency
|
|
||||||
if currency == 'bitcoin':
|
|
||||||
res = bitcoinRPC.redeem_contract(contract, secret)
|
|
||||||
else:
|
|
||||||
res = zcashRPC.redeem_contract(contract, secret)
|
|
||||||
return res
|
|
||||||
|
|
||||||
def parse_secret(chain, txid):
|
|
||||||
if chain == 'bitcoin':
|
|
||||||
secret = bitcoinRPC.parse_secret(txid)
|
|
||||||
else:
|
|
||||||
secret = zcashRPC.parse_secret(txid)
|
|
||||||
return secret
|
|
||||||
|
|
||||||
#### Main functions determining user flow from command line
|
#### Main functions determining user flow from command line
|
||||||
def buyer_redeem(trade):
|
def buyer_redeem(trade):
|
||||||
userInput.authorize_buyer_redeem(trade)
|
userInput.authorize_buyer_redeem(trade)
|
||||||
|
@ -148,7 +181,6 @@ def buyer_redeem(trade):
|
||||||
def seller_redeem_p2sh(trade, secret):
|
def seller_redeem_p2sh(trade, secret):
|
||||||
buy = trade.buy
|
buy = trade.buy
|
||||||
userInput.authorize_seller_redeem(buy)
|
userInput.authorize_seller_redeem(buy)
|
||||||
|
|
||||||
if trade.sell.get_status() == 'redeemed':
|
if trade.sell.get_status() == 'redeemed':
|
||||||
print("You already redeemed the funds and acquired {0} {1}".format(buy.amount, buy.currency))
|
print("You already redeemed the funds and acquired {0} {1}".format(buy.amount, buy.currency))
|
||||||
exit()
|
exit()
|
||||||
|
@ -203,11 +235,10 @@ def initialize_trade(tradeid, **kwargs):
|
||||||
print(trade.buy.__dict__)
|
print(trade.buy.__dict__)
|
||||||
return tradeid, trade
|
return tradeid, trade
|
||||||
|
|
||||||
|
def seller_init(tradeid, trade, network):
|
||||||
def seller_init(tradeid, trade):
|
|
||||||
secret = generate_password()
|
secret = generate_password()
|
||||||
db.save_secret(tradeid, secret)
|
db.save_secret(tradeid, secret)
|
||||||
print("\nGenerated a secret preimage to lock funds. This will only be stored locally: ", secret)
|
print("Generated a secret preimage to lock funds. This will only be stored locally: {0}".format(secret))
|
||||||
|
|
||||||
hash_of_secret = sha256(secret)
|
hash_of_secret = sha256(secret)
|
||||||
# TODO: Implement locktimes and mock block passage of time
|
# TODO: Implement locktimes and mock block passage of time
|
||||||
|
@ -220,5 +251,5 @@ def seller_init(tradeid, trade):
|
||||||
create_buy_p2sh(trade, hash_of_secret, buy_locktime)
|
create_buy_p2sh(trade, hash_of_secret, buy_locktime)
|
||||||
|
|
||||||
trade.commitment = b2x(hash_of_secret)
|
trade.commitment = b2x(hash_of_secret)
|
||||||
print("TRADE after seller init", trade.toJSON())
|
print("TRADE after seller init: {0}".format(trade.toJSON()))
|
||||||
return trade
|
return trade
|
||||||
|
|
|
@ -2,6 +2,7 @@ from xcat.utils import *
|
||||||
from xcat.db import *
|
from xcat.db import *
|
||||||
from xcat.bitcoinRPC import bitcoinProxy
|
from xcat.bitcoinRPC import bitcoinProxy
|
||||||
from xcat.zcashRPC import zcashProxy
|
from xcat.zcashRPC import zcashProxy
|
||||||
|
from xcat.xcatconf import *
|
||||||
|
|
||||||
def enter_trade_id():
|
def enter_trade_id():
|
||||||
tradeid = input("Enter a unique identifier for this trade: ")
|
tradeid = input("Enter a unique identifier for this trade: ")
|
||||||
|
|
|
@ -17,9 +17,11 @@ ADDRS = {
|
||||||
"zcash": "tmTF7LMLjvEsGdcepWPUsh4vgJNrKMWwEyc"
|
"zcash": "tmTF7LMLjvEsGdcepWPUsh4vgJNrKMWwEyc"
|
||||||
},
|
},
|
||||||
"fulfiller": {
|
"fulfiller": {
|
||||||
"bitcoin": "mm2smEJjRN4xoijEfpb5XvYd8e3EYWezom",
|
"bitcoin": "mn2boR7rYq9DaAWWrVN5MazHKFyf7UhdyU",
|
||||||
"zcash": "tmPwPdceaJAHQn7UiRCVnJ5tXBXHVqWMkis"
|
"zcash": "tmErB22A1G74aq32aAh5AoqgQSJsAAAdT2p"
|
||||||
},
|
},
|
||||||
"amounts": {'buy': {'currency': 'zcash', 'amount': 0.02}, 'sell': {'currency': 'bitcoin', 'amount': 0.01}}
|
"amounts": {'buy': {'currency': 'zcash', 'amount': 0.02}, 'sell': {'currency': 'bitcoin', 'amount': 0.01}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NETWORK = 'testnet'
|
||||||
|
|
|
@ -12,6 +12,7 @@ from zcash.core import b2x, lx, x, b2lx, COIN, COutPoint, CMutableTxOut, CMutabl
|
||||||
from zcash.core.script import CScript, OP_DUP, OP_IF, OP_ELSE, OP_ENDIF, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG, SignatureHash, SIGHASH_ALL, OP_FALSE, OP_DROP, OP_CHECKLOCKTIMEVERIFY, OP_SHA256, OP_TRUE
|
from zcash.core.script import CScript, OP_DUP, OP_IF, OP_ELSE, OP_ENDIF, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG, SignatureHash, SIGHASH_ALL, OP_FALSE, OP_DROP, OP_CHECKLOCKTIMEVERIFY, OP_SHA256, OP_TRUE
|
||||||
from zcash.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH
|
from zcash.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH
|
||||||
from zcash.wallet import CBitcoinAddress, CBitcoinSecret, P2SHBitcoinAddress, P2PKHBitcoinAddress
|
from zcash.wallet import CBitcoinAddress, CBitcoinSecret, P2SHBitcoinAddress, P2PKHBitcoinAddress
|
||||||
|
import logging
|
||||||
|
|
||||||
from xcat.utils import x2s
|
from xcat.utils import x2s
|
||||||
|
|
||||||
|
@ -104,6 +105,7 @@ class zcashProxy():
|
||||||
for tx in txs:
|
for tx in txs:
|
||||||
raw = self.zcashd.gettransaction(lx(tx['txid']))['hex']
|
raw = self.zcashd.gettransaction(lx(tx['txid']))['hex']
|
||||||
decoded = self.zcashd.decoderawtransaction(raw)
|
decoded = self.zcashd.decoderawtransaction(raw)
|
||||||
|
# print("TXINFO", decoded['vin'][0])
|
||||||
if('txid' in decoded['vin'][0]):
|
if('txid' in decoded['vin'][0]):
|
||||||
sendid = decoded['vin'][0]['txid']
|
sendid = decoded['vin'][0]['txid']
|
||||||
if (sendid == fundtx_input ):
|
if (sendid == fundtx_input ):
|
||||||
|
@ -112,14 +114,42 @@ class zcashProxy():
|
||||||
print("Redeem transaction with secret not found")
|
print("Redeem transaction with secret not found")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# def find_secret(self, p2sh, fundtx_input):
|
||||||
|
# print("In find secret zcashrpc")
|
||||||
|
# txs = self.zcashd.call('listtransactions', "*", 200, 0, True)
|
||||||
|
# for tx in txs:
|
||||||
|
# if tx['address'] == p2sh: # Only check txs involving imported p2sh
|
||||||
|
# raw = self.zcashd.gettransaction(lx(tx['txid']))['hex']
|
||||||
|
# decoded = self.zcashd.decoderawtransaction(raw)
|
||||||
|
# print('decoded', decoded)
|
||||||
|
# if('txid' in decoded['vin'][0]):
|
||||||
|
# sendid = decoded['vin'][0]['txid']
|
||||||
|
# print("sendid", sendid)
|
||||||
|
# if (sendid == fundtx_input ):
|
||||||
|
# print("Found funding zcash tx: ", sendid)
|
||||||
|
# res = self.parse_secret(lx(tx['txid']))
|
||||||
|
# secret = res[0]
|
||||||
|
# redeemPubkey = res[1]
|
||||||
|
# if secret is None:
|
||||||
|
# print("Secret not found")
|
||||||
|
# res = self.validateaddress(redeemPubkey)
|
||||||
|
# if res['ismine']:
|
||||||
|
# print("Funding tx already refunded. Sent to your address {0}".format(redeemPubkey))
|
||||||
|
# logging.debug("Redeem transaction with secret not found")
|
||||||
|
# return
|
||||||
|
|
||||||
def parse_secret(self, txid):
|
def parse_secret(self, txid):
|
||||||
raw = self.zcashd.gettransaction(txid, True)['hex']
|
raw = self.zcashd.gettransaction(txid, True)['hex']
|
||||||
decoded = self.zcashd.decoderawtransaction(raw)
|
decoded = self.zcashd.decoderawtransaction(raw)
|
||||||
scriptSig = decoded['vin'][0]['scriptSig']
|
scriptSig = decoded['vin'][0]['scriptSig']
|
||||||
asm = scriptSig['asm'].split(" ")
|
asm = scriptSig['asm'].split(" ")
|
||||||
pubkey = asm[1]
|
pubkey = asm[1]
|
||||||
|
try:
|
||||||
secret = x2s(asm[2])
|
secret = x2s(asm[2])
|
||||||
redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey))
|
except:
|
||||||
|
secret = None
|
||||||
|
self.redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey))
|
||||||
|
print("redeemPubkey: ", self.redeemPubkey)
|
||||||
return secret
|
return secret
|
||||||
|
|
||||||
def redeem_contract(self, contract, secret):
|
def redeem_contract(self, contract, secret):
|
||||||
|
@ -136,18 +166,23 @@ class zcashProxy():
|
||||||
p2sh = P2SHBitcoinAddress(p2sh)
|
p2sh = P2SHBitcoinAddress(p2sh)
|
||||||
if fundtx['address'] == p2sh:
|
if fundtx['address'] == p2sh:
|
||||||
print("Found {0} in p2sh {1}, redeeming...".format(amount, p2sh))
|
print("Found {0} in p2sh {1}, redeeming...".format(amount, p2sh))
|
||||||
|
|
||||||
# Where can you find redeemblocknum in the transaction?
|
# Where can you find redeemblocknum in the transaction?
|
||||||
# redeemblocknum = find_redeemblocknum(contract)
|
# redeemblocknum = find_redeemblocknum(contract)
|
||||||
blockcount = self.zcashd.getblockcount()
|
blockcount = self.zcashd.getblockcount()
|
||||||
print("\nCurrent blocknum at time of redeem on Zcash:", blockcount)
|
print("\nCurrent blocknum at time of redeem on Zcash:", blockcount)
|
||||||
if blockcount < contract.redeemblocknum:
|
if blockcount < contract.redeemblocknum:
|
||||||
|
return self.redeem(contract, fundtx, secret)
|
||||||
|
else:
|
||||||
|
print("nLocktime exceeded, refunding")
|
||||||
|
return self.refund(contract)
|
||||||
|
else:
|
||||||
|
print("No contract for this p2sh found in database", p2sh)
|
||||||
|
|
||||||
|
def redeem(self, contract, fundtx, secret):
|
||||||
# TODO: parse the script once, up front.
|
# TODO: parse the script once, up front.
|
||||||
redeemPubKey = self.find_redeemAddr(contract)
|
redeemPubKey = self.find_redeemAddr(contract)
|
||||||
|
|
||||||
print('redeemPubKey', redeemPubKey)
|
print('redeemPubKey', redeemPubKey)
|
||||||
zec_redeemScript = CScript(x(contract.redeemScript))
|
zec_redeemScript = CScript(x(contract.redeemScript))
|
||||||
|
|
||||||
txin = CMutableTxIn(fundtx['outpoint'])
|
txin = CMutableTxIn(fundtx['outpoint'])
|
||||||
txout = CMutableTxOut(fundtx['amount'] - FEE, redeemPubKey.to_scriptPubKey())
|
txout = CMutableTxOut(fundtx['amount'] - FEE, redeemPubKey.to_scriptPubKey())
|
||||||
# Create the unsigned raw transaction.
|
# Create the unsigned raw transaction.
|
||||||
|
@ -167,16 +202,32 @@ class zcashProxy():
|
||||||
redeem_tx = b2x(lx(b2x(txid)))
|
redeem_tx = b2x(lx(b2x(txid)))
|
||||||
fund_tx = str(fundtx['outpoint'])
|
fund_tx = str(fundtx['outpoint'])
|
||||||
return {"redeem_tx": redeem_tx, "fund_tx": fund_tx}
|
return {"redeem_tx": redeem_tx, "fund_tx": fund_tx}
|
||||||
else:
|
|
||||||
print("nLocktime exceeded, refunding")
|
def refund(self, contract):
|
||||||
|
fundtx = self.find_transaction_to_address(contract.p2sh)
|
||||||
|
print("Fund tx found in refund: ", fundtx)
|
||||||
refundPubKey = self.find_refundAddr(contract)
|
refundPubKey = self.find_refundAddr(contract)
|
||||||
print('refundPubKey', refundPubKey)
|
print('refundPubKey: {0}'.format(refundPubKey))
|
||||||
txid = self.zcashd.sendtoaddress(refundPubKey, fundtx['amount'] - FEE)
|
|
||||||
|
redeemScript = CScript(x(contract.redeemScript))
|
||||||
|
txin = CMutableTxIn(fundtx['outpoint'])
|
||||||
|
txout = CMutableTxOut(fundtx['amount'] - FEE, refundPubKey.to_scriptPubKey())
|
||||||
|
# Create the unsigned raw transaction.
|
||||||
|
tx = CMutableTransaction([txin], [txout])
|
||||||
|
sighash = SignatureHash(redeemScript, tx, 0, SIGHASH_ALL)
|
||||||
|
privkey = self.zcashd.dumpprivkey(refundPubKey)
|
||||||
|
sig = privkey.sign(sighash) + bytes([SIGHASH_ALL])
|
||||||
|
# Sign without secret
|
||||||
|
txin.scriptSig = CScript([sig, privkey.pub, OP_FALSE, redeemScript])
|
||||||
|
# txin.nSequence = 2185
|
||||||
|
txin_scriptPubKey = redeemScript.to_p2sh_scriptPubKey()
|
||||||
|
print('Raw redeem transaction hex: {0}'.format(b2x(tx.serialize())))
|
||||||
|
res = VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,))
|
||||||
|
print("Script verified, sending raw transaction... (NOT)", res)
|
||||||
|
txid = self.zcashd.sendrawtransaction(tx)
|
||||||
refund_tx = b2x(lx(b2x(txid)))
|
refund_tx = b2x(lx(b2x(txid)))
|
||||||
fund_tx = str(fundtx['outpoint'])
|
fund_tx = str(fundtx['outpoint'])
|
||||||
return {"refund_tx": refund_tx, "fund_tx": fund_tx}
|
return {"refund_tx": refund_tx, "fund_tx": fund_tx}
|
||||||
else:
|
|
||||||
print("No contract for this p2sh found in database", p2sh)
|
|
||||||
|
|
||||||
def parse_script(self, script_hex):
|
def parse_script(self, script_hex):
|
||||||
redeemScript = self.zcashd.decodescript(script_hex)
|
redeemScript = self.zcashd.decodescript(script_hex)
|
||||||
|
|
Loading…
Reference in New Issue