Merge branch 'master' of https://github.com/zcash-hackworks/zbxcat into better-tests

This commit is contained in:
James Prestwich 2017-09-11 15:30:33 -06:00
commit 7ccc8084b2
No known key found for this signature in database
GPG Key ID: 519E010A79028CCC
6 changed files with 227 additions and 88 deletions

View File

@ -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):

View File

@ -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)

View File

@ -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

View File

@ -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: ")

View File

@ -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'

View File

@ -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(' ')