This commit is contained in:
James Prestwich 2017-09-14 14:40:07 -06:00
commit 772f1b857b
No known key found for this signature in database
GPG Key ID: 519E010A79028CCC
6 changed files with 233 additions and 102 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,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):

View File

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

View File

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

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