Merge branch 'master' of https://github.com/zcash-hackworks/zbxcat into better-tests
This commit is contained in:
commit
7ccc8084b2
|
@ -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,38 +165,64 @@ 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))
|
|
||||||
txin = CMutableTxIn(fundtx['outpoint'])
|
|
||||||
txout = CMutableTxOut(fundtx['amount'] - FEE, redeemPubKey.to_scriptPubKey())
|
|
||||||
# Create the unsigned raw transaction.
|
|
||||||
tx = CMutableTransaction([txin], [txout])
|
|
||||||
sighash = SignatureHash(zec_redeemScript, tx, 0, SIGHASH_ALL)
|
|
||||||
# TODO: protect privkey better, separate signing from rawtx creation
|
|
||||||
privkey = self.bitcoind.dumpprivkey(redeemPubKey)
|
|
||||||
sig = privkey.sign(sighash) + bytes([SIGHASH_ALL])
|
|
||||||
preimage = secret.encode('utf-8')
|
|
||||||
txin.scriptSig = CScript([sig, privkey.pub, preimage, OP_TRUE, zec_redeemScript])
|
|
||||||
|
|
||||||
# print("txin.scriptSig", b2x(txin.scriptSig))
|
|
||||||
txin_scriptPubKey = zec_redeemScript.to_p2sh_scriptPubKey()
|
|
||||||
print('Raw redeem transaction hex: ', b2x(tx.serialize()))
|
|
||||||
VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,))
|
|
||||||
print("Script verified, sending raw transaction...")
|
|
||||||
txid = self.bitcoind.sendrawtransaction(tx)
|
|
||||||
fund_tx = str(fundtx['outpoint'])
|
|
||||||
redeem_tx = b2x(lx(b2x(txid)))
|
|
||||||
return {"redeem_tx": redeem_tx, "fund_tx": fund_tx}
|
|
||||||
else:
|
else:
|
||||||
print("nLocktime exceeded, refunding")
|
print("nLocktime exceeded, refunding")
|
||||||
print('refundPubKey', refundPubKey)
|
return self.refund(contract)
|
||||||
txid = self.bitcoind.sendtoaddress(refundPubKey, fundtx['amount'] - FEE)
|
|
||||||
fund_tx = str(fundtx['outpoint'])
|
|
||||||
refund_tx = b2x(lx(b2x(txid)))
|
|
||||||
return {"refund_tx": refund_tx, "fund_tx": fund_tx}
|
|
||||||
else:
|
else:
|
||||||
print("No contract for this p2sh found in database", p2sh)
|
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'])
|
||||||
|
txout = CMutableTxOut(fundtx['amount'] - FEE, self.redeemPubKey.to_scriptPubKey())
|
||||||
|
# Create the unsigned raw transaction.
|
||||||
|
tx = CMutableTransaction([txin], [txout])
|
||||||
|
sighash = SignatureHash(redeemScript, tx, 0, SIGHASH_ALL)
|
||||||
|
# TODO: protect privkey better, separate signing from rawtx creation
|
||||||
|
privkey = self.bitcoind.dumpprivkey(self.redeemPubKey)
|
||||||
|
sig = privkey.sign(sighash) + bytes([SIGHASH_ALL])
|
||||||
|
preimage = secret.encode('utf-8')
|
||||||
|
txin.scriptSig = CScript([sig, privkey.pub, preimage, OP_TRUE, redeemScript])
|
||||||
|
|
||||||
|
# print("txin.scriptSig", b2x(txin.scriptSig))
|
||||||
|
txin_scriptPubKey = redeemScript.to_p2sh_scriptPubKey()
|
||||||
|
print('Raw redeem transaction hex: ', b2x(tx.serialize()))
|
||||||
|
VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,))
|
||||||
|
print("Script verified, sending raw transaction...")
|
||||||
|
txid = self.bitcoind.sendrawtransaction(tx)
|
||||||
|
fund_tx = str(fundtx['outpoint'])
|
||||||
|
redeem_tx = b2x(lx(b2x(txid)))
|
||||||
|
return {"redeem_tx": redeem_tx, "fund_tx": fund_tx}
|
||||||
|
|
||||||
|
def refund(self, contract):
|
||||||
|
fundtx = self.find_transaction_to_address(contract.p2sh)
|
||||||
|
print("Fund tx found in refund: ", fundtx)
|
||||||
|
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)))
|
||||||
|
fund_tx = str(fundtx['outpoint'])
|
||||||
|
return {"refund_tx": refund_tx, "fund_tx": fund_tx}
|
||||||
|
|
||||||
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)
|
||||||
scriptarray = redeemScript['asm'].split(' ')
|
scriptarray = redeemScript['asm'].split(' ')
|
||||||
|
@ -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):
|
||||||
|
|
33
xcat/cli.py
33
xcat/cli.py
|
@ -32,9 +32,11 @@ 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
|
# Remove from db? Or just from temporary file storage
|
||||||
utils.cleanup(tradeid)
|
utils.cleanup(tradeid)
|
||||||
|
@ -123,6 +125,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")
|
||||||
|
|
||||||
|
|
||||||
|
@ -211,9 +214,12 @@ def newtrade(tradeid, **kwargs):
|
||||||
protocol = Protocol()
|
protocol = Protocol()
|
||||||
print("Creating new XCAT trade...")
|
print("Creating new XCAT trade...")
|
||||||
utils.erase_trade()
|
utils.erase_trade()
|
||||||
tradeid, trade = protocol.initialize_trade(tradeid, conf=kwargs['conf'])
|
tradeid, trade = protocol.initialize_trade(
|
||||||
print("Trade", trade)
|
tradeid,
|
||||||
trade = protocol.seller_init(tradeid, trade)
|
conf=kwargs['conf'],
|
||||||
|
network=kwargs['network'])
|
||||||
|
print("New trade created: {0}".format(trade))
|
||||||
|
trade = protocol.seller_init(tradeid, trade, network=kwargs['network'])
|
||||||
print("\nUse 'xcat exporttrade [tradeid]' to export the trade and sent "
|
print("\nUse 'xcat exporttrade [tradeid]' to export the trade and sent "
|
||||||
"to the buyer.\n")
|
"to the buyer.\n")
|
||||||
save_state(trade, tradeid)
|
save_state(trade, tradeid)
|
||||||
|
@ -260,6 +266,17 @@ def main():
|
||||||
|
|
||||||
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:
|
||||||
|
@ -294,9 +311,9 @@ def main():
|
||||||
utils.throw("Usage: newtrade [tradeid]")
|
utils.throw("Usage: newtrade [tradeid]")
|
||||||
tradeid = args.arguments[0]
|
tradeid = args.arguments[0]
|
||||||
if args.conf is None:
|
if args.conf is 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")
|
||||||
|
@ -308,8 +325,10 @@ def main():
|
||||||
tradeid = args.arguments[0]
|
tradeid = args.arguments[0]
|
||||||
checkBuyStatus(tradeid)
|
checkBuyStatus(tradeid)
|
||||||
elif command == "step3":
|
elif command == "step3":
|
||||||
|
protocol.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)
|
||||||
|
|
|
@ -5,6 +5,7 @@ from xcat.xcatconf import ADDRS
|
||||||
from xcat.trades import Contract, Trade
|
from xcat.trades import Contract, Trade
|
||||||
from xcat.bitcoinRPC import bitcoinProxy
|
from xcat.bitcoinRPC import bitcoinProxy
|
||||||
from xcat.zcashRPC import zcashProxy
|
from xcat.zcashRPC import zcashProxy
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
class Protocol():
|
class Protocol():
|
||||||
|
@ -13,20 +14,33 @@ class Protocol():
|
||||||
self.bitcoinRPC = bitcoinProxy()
|
self.bitcoinRPC = bitcoinProxy()
|
||||||
self.zcashRPC = zcashProxy()
|
self.zcashRPC = zcashProxy()
|
||||||
|
|
||||||
|
def generate(self, num):
|
||||||
|
self.bitcoinRPC.generate(num)
|
||||||
|
self.zcashRPC.generate(num)
|
||||||
|
|
||||||
def is_myaddr(self, address):
|
def is_myaddr(self, address):
|
||||||
|
# Handle differnt network prefixes
|
||||||
if address[:1] == 'm':
|
if address[:1] == 'm':
|
||||||
status = self.bitcoinRPC.validateaddress(address)
|
status = self.bitcoinRPC.validateaddress(address)
|
||||||
else:
|
else:
|
||||||
status = self.zcashRPC.validateaddress(address)
|
status = self.zcashRPC.validateaddress(address)
|
||||||
status = status['ismine']
|
|
||||||
|
logging.debug("Address status: ", status)
|
||||||
|
|
||||||
|
if not status['isvalid']:
|
||||||
|
raise ValueError("Invalid address: %s" % address)
|
||||||
|
elif 'ismine' in status:
|
||||||
|
status = status['ismine']
|
||||||
# print("Address {0} is mine: {1}".format(address, status))
|
# print("Address {0} is mine: {1}".format(address, status))
|
||||||
return status
|
return status
|
||||||
|
|
||||||
def find_secret_from_fundtx(self, currency, p2sh, fundtx):
|
def find_secret_from_fundtx(self, currency, p2sh, fundtx):
|
||||||
if currency == 'bitcoin':
|
if currency == 'bitcoin':
|
||||||
secret = self.bitcoinRPC.find_secret(p2sh, fundtx)
|
secret = self.bitcoinRPC.find_secret(p2sh, fundtx)
|
||||||
else:
|
elif currency == 'zcash':
|
||||||
secret = self.zcashRPC.find_secret(p2sh, fundtx)
|
secret = self.zcashRPC.find_secret(p2sh, fundtx)
|
||||||
|
else:
|
||||||
|
raise ValueError('Currency not recognized: %s' % currency)
|
||||||
return secret
|
return secret
|
||||||
|
|
||||||
def import_addrs(self, trade):
|
def import_addrs(self, trade):
|
||||||
|
@ -37,17 +51,21 @@ class Protocol():
|
||||||
if currency == 'bitcoin':
|
if currency == 'bitcoin':
|
||||||
print("Checking funds in Bitcoin p2sh")
|
print("Checking funds in Bitcoin p2sh")
|
||||||
return self.bitcoinRPC.check_funds(address)
|
return self.bitcoinRPC.check_funds(address)
|
||||||
else:
|
elif currency == 'zcash':
|
||||||
print("Checking funds in Zcash p2sh")
|
print("Checking funds in Zcash p2sh")
|
||||||
return self.zcashRPC.check_funds(address)
|
return self.zcashRPC.check_funds(address)
|
||||||
|
else:
|
||||||
|
raise ValueError('Currency not recognized: %s' % currency)
|
||||||
|
|
||||||
def check_fund_status(self, currency, address):
|
def check_fund_status(self, currency, address):
|
||||||
if currency == 'bitcoin':
|
if currency == 'bitcoin':
|
||||||
print("Checking funds in Bitcoin p2sh")
|
print("Checking funds in Bitcoin p2sh")
|
||||||
return self.bitcoinRPC.get_fund_status(address)
|
return self.bitcoinRPC.get_fund_status(address)
|
||||||
else:
|
elif currency == 'zcash':
|
||||||
print("Checking funds in Zcash p2sh")
|
print("Checking funds in Zcash p2sh")
|
||||||
return self.zcashRPC.get_fund_status(address)
|
return self.zcashRPC.get_fund_status(address)
|
||||||
|
else:
|
||||||
|
raise ValueError('Currency not recognized: %s' % 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):
|
||||||
|
@ -66,16 +84,20 @@ class Protocol():
|
||||||
if currency == 'bitcoin':
|
if currency == 'bitcoin':
|
||||||
sell_p2sh = self.bitcoinRPC.hashtimelockcontract(
|
sell_p2sh = self.bitcoinRPC.hashtimelockcontract(
|
||||||
funder, redeemer, commitment, locktime)
|
funder, redeemer, commitment, locktime)
|
||||||
else:
|
elif currency == 'zcash':
|
||||||
sell_p2sh = self.zcashRPC.hashtimelockcontract(
|
sell_p2sh = self.zcashRPC.hashtimelockcontract(
|
||||||
funder, redeemer, commitment, locktime)
|
funder, redeemer, commitment, locktime)
|
||||||
|
else:
|
||||||
|
raise ValueError('Currency not recognized: %s' % currency)
|
||||||
return sell_p2sh
|
return sell_p2sh
|
||||||
|
|
||||||
def fund_htlc(self, currency, p2sh, amount):
|
def fund_htlc(self, currency, p2sh, amount):
|
||||||
if currency == 'bitcoin':
|
if currency == 'bitcoin':
|
||||||
txid = self.bitcoinRPC.fund_htlc(p2sh, amount)
|
txid = self.bitcoinRPC.fund_htlc(p2sh, amount)
|
||||||
else:
|
elif currency == 'zcash':
|
||||||
txid = self.zcashRPC.fund_htlc(p2sh, amount)
|
txid = self.zcashRPC.fund_htlc(p2sh, amount)
|
||||||
|
else:
|
||||||
|
raise ValueError('Currency not recognized: %s' % currency)
|
||||||
return txid
|
return txid
|
||||||
|
|
||||||
def fund_contract(self, contract):
|
def fund_contract(self, contract):
|
||||||
|
@ -124,15 +146,29 @@ class Protocol():
|
||||||
currency = contract.currency
|
currency = contract.currency
|
||||||
if currency == 'bitcoin':
|
if currency == 'bitcoin':
|
||||||
res = self.bitcoinRPC.redeem_contract(contract, secret)
|
res = self.bitcoinRPC.redeem_contract(contract, secret)
|
||||||
else:
|
elif currency == 'zcash':
|
||||||
res = self.zcashRPC.redeem_contract(contract, secret)
|
res = self.zcashRPC.redeem_contract(contract, secret)
|
||||||
|
else:
|
||||||
|
raise ValueError('Currency not recognized: %s' % currency)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def parse_secret(self, chain, txid):
|
def refund_contract(self, contract):
|
||||||
if chain == 'bitcoin':
|
currency = contract.currency
|
||||||
secret = self.bitcoinRPC.parse_secret(txid)
|
if currency == 'bitcoin':
|
||||||
|
res = self.bitcoinRPC.refund(contract)
|
||||||
|
elif currency == 'zcash':
|
||||||
|
res = self.zcashRPC.refund(contract)
|
||||||
else:
|
else:
|
||||||
|
raise ValueError('Currency not recognized: %s', currency)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def parse_secret(self, currency, txid):
|
||||||
|
if currency == 'bitcoin':
|
||||||
|
secret = self.bitcoinRPC.parse_secret(txid)
|
||||||
|
elif currency == 'zcash':
|
||||||
secret = self.zcashRPC.parse_secret(txid)
|
secret = self.zcashRPC.parse_secret(txid)
|
||||||
|
else:
|
||||||
|
raise ValueError('Currency not recognized: %s', currency)
|
||||||
return secret
|
return secret
|
||||||
|
|
||||||
# Main functions determining user flow from command line
|
# Main functions determining user flow from command line
|
||||||
|
@ -185,7 +221,7 @@ class Protocol():
|
||||||
buy_p2sh_balance,
|
buy_p2sh_balance,
|
||||||
buy.currency)
|
buy.currency)
|
||||||
print("Buy amt:", buy.amount)
|
print("Buy amt:", buy.amount)
|
||||||
txid = fund_buy_contract(trade)
|
txid = self.fund_buy_contract(trade)
|
||||||
print("Fund tx txid:", txid)
|
print("Fund tx txid:", txid)
|
||||||
else:
|
else:
|
||||||
print("It looks like you've already funded the contract to buy "
|
print("It looks like you've already funded the contract to buy "
|
||||||
|
@ -193,7 +229,7 @@ class Protocol():
|
||||||
"{0}.".format(buy_p2sh_balance, buy.currency))
|
"{0}.".format(buy_p2sh_balance, buy.currency))
|
||||||
print("Please wait for the seller to remove your funds from "
|
print("Please wait for the seller to remove your funds from "
|
||||||
"escrow to complete the trade.")
|
"escrow to complete the trade.")
|
||||||
print_trade('buyer')
|
self.print_trade('buyer')
|
||||||
|
|
||||||
def initialize_trade(self, tradeid, **kwargs):
|
def initialize_trade(self, tradeid, **kwargs):
|
||||||
trade = Trade()
|
trade = Trade()
|
||||||
|
@ -228,7 +264,7 @@ class Protocol():
|
||||||
secret = utils.generate_password()
|
secret = utils.generate_password()
|
||||||
db.save_secret(tradeid, secret)
|
db.save_secret(tradeid, secret)
|
||||||
print("\nGenerated a secret preimage to lock funds. This will only "
|
print("\nGenerated a secret preimage to lock funds. This will only "
|
||||||
"be stored locally: ", secret)
|
"be stored locally: {0}".format(secret))
|
||||||
|
|
||||||
hash_of_secret = utils.sha256(secret)
|
hash_of_secret = utils.sha256(secret)
|
||||||
# TODO: Implement locktimes and mock block passage of time
|
# TODO: Implement locktimes and mock block passage of time
|
||||||
|
@ -241,5 +277,5 @@ class Protocol():
|
||||||
self.create_buy_p2sh(trade, hash_of_secret, buy_locktime)
|
self.create_buy_p2sh(trade, hash_of_secret, buy_locktime)
|
||||||
|
|
||||||
trade.commitment = utils.b2x(hash_of_secret)
|
trade.commitment = utils.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'
|
||||||
|
|
119
xcat/zcashRPC.py
119
xcat/zcashRPC.py
|
@ -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]
|
||||||
secret = x2s(asm[2])
|
try:
|
||||||
redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey))
|
secret = x2s(asm[2])
|
||||||
|
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,48 +166,69 @@ 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:
|
||||||
# TODO: parse the script once, up front.
|
return self.redeem(contract, fundtx, secret)
|
||||||
redeemPubKey = self.find_redeemAddr(contract)
|
|
||||||
|
|
||||||
print('redeemPubKey', redeemPubKey)
|
|
||||||
zec_redeemScript = CScript(x(contract.redeemScript))
|
|
||||||
|
|
||||||
txin = CMutableTxIn(fundtx['outpoint'])
|
|
||||||
txout = CMutableTxOut(fundtx['amount'] - FEE, redeemPubKey.to_scriptPubKey())
|
|
||||||
# Create the unsigned raw transaction.
|
|
||||||
tx = CMutableTransaction([txin], [txout])
|
|
||||||
sighash = SignatureHash(zec_redeemScript, tx, 0, SIGHASH_ALL)
|
|
||||||
# TODO: figure out how to better protect privkey
|
|
||||||
privkey = self.zcashd.dumpprivkey(redeemPubKey)
|
|
||||||
sig = privkey.sign(sighash) + bytes([SIGHASH_ALL])
|
|
||||||
print("SECRET", secret)
|
|
||||||
preimage = secret.encode('utf-8')
|
|
||||||
txin.scriptSig = CScript([sig, privkey.pub, preimage, OP_TRUE, zec_redeemScript])
|
|
||||||
txin_scriptPubKey = zec_redeemScript.to_p2sh_scriptPubKey()
|
|
||||||
print('Raw redeem transaction hex: ', b2x(tx.serialize()))
|
|
||||||
VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,))
|
|
||||||
print("Script verified, sending raw redeem transaction...")
|
|
||||||
txid = self.zcashd.sendrawtransaction(tx)
|
|
||||||
redeem_tx = b2x(lx(b2x(txid)))
|
|
||||||
fund_tx = str(fundtx['outpoint'])
|
|
||||||
return {"redeem_tx": redeem_tx, "fund_tx": fund_tx}
|
|
||||||
else:
|
else:
|
||||||
print("nLocktime exceeded, refunding")
|
print("nLocktime exceeded, refunding")
|
||||||
refundPubKey = self.find_refundAddr(contract)
|
return self.refund(contract)
|
||||||
print('refundPubKey', refundPubKey)
|
|
||||||
txid = self.zcashd.sendtoaddress(refundPubKey, fundtx['amount'] - FEE)
|
|
||||||
refund_tx = b2x(lx(b2x(txid)))
|
|
||||||
fund_tx = str(fundtx['outpoint'])
|
|
||||||
return {"refund_tx": refund_tx, "fund_tx": fund_tx}
|
|
||||||
else:
|
else:
|
||||||
print("No contract for this p2sh found in database", p2sh)
|
print("No contract for this p2sh found in database", p2sh)
|
||||||
|
|
||||||
|
def redeem(self, contract, fundtx, secret):
|
||||||
|
# TODO: parse the script once, up front.
|
||||||
|
redeemPubKey = self.find_redeemAddr(contract)
|
||||||
|
print('redeemPubKey', redeemPubKey)
|
||||||
|
zec_redeemScript = CScript(x(contract.redeemScript))
|
||||||
|
txin = CMutableTxIn(fundtx['outpoint'])
|
||||||
|
txout = CMutableTxOut(fundtx['amount'] - FEE, redeemPubKey.to_scriptPubKey())
|
||||||
|
# Create the unsigned raw transaction.
|
||||||
|
tx = CMutableTransaction([txin], [txout])
|
||||||
|
sighash = SignatureHash(zec_redeemScript, tx, 0, SIGHASH_ALL)
|
||||||
|
# TODO: figure out how to better protect privkey
|
||||||
|
privkey = self.zcashd.dumpprivkey(redeemPubKey)
|
||||||
|
sig = privkey.sign(sighash) + bytes([SIGHASH_ALL])
|
||||||
|
print("SECRET", secret)
|
||||||
|
preimage = secret.encode('utf-8')
|
||||||
|
txin.scriptSig = CScript([sig, privkey.pub, preimage, OP_TRUE, zec_redeemScript])
|
||||||
|
txin_scriptPubKey = zec_redeemScript.to_p2sh_scriptPubKey()
|
||||||
|
print('Raw redeem transaction hex: ', b2x(tx.serialize()))
|
||||||
|
VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,))
|
||||||
|
print("Script verified, sending raw redeem transaction...")
|
||||||
|
txid = self.zcashd.sendrawtransaction(tx)
|
||||||
|
redeem_tx = b2x(lx(b2x(txid)))
|
||||||
|
fund_tx = str(fundtx['outpoint'])
|
||||||
|
return {"redeem_tx": redeem_tx, "fund_tx": fund_tx}
|
||||||
|
|
||||||
|
def refund(self, contract):
|
||||||
|
fundtx = self.find_transaction_to_address(contract.p2sh)
|
||||||
|
print("Fund tx found in refund: ", fundtx)
|
||||||
|
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.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)))
|
||||||
|
fund_tx = str(fundtx['outpoint'])
|
||||||
|
return {"refund_tx": refund_tx, "fund_tx": fund_tx}
|
||||||
|
|
||||||
def parse_script(self, script_hex):
|
def parse_script(self, script_hex):
|
||||||
redeemScript = self.zcashd.decodescript(script_hex)
|
redeemScript = self.zcashd.decodescript(script_hex)
|
||||||
scriptarray = redeemScript['asm'].split(' ')
|
scriptarray = redeemScript['asm'].split(' ')
|
||||||
|
|
Loading…
Reference in New Issue