From 1e14c0a7f442c3d59bc387d09cde0a9979ba6ac9 Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Tue, 29 Aug 2017 12:41:17 -0700 Subject: [PATCH 01/10] Raise ValueError for unrecognized blockchain --- xcat/protocol.py | 69 ++++++++++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 26 deletions(-) diff --git a/xcat/protocol.py b/xcat/protocol.py index 27f8721..de41c24 100644 --- a/xcat/protocol.py +++ b/xcat/protocol.py @@ -13,7 +13,7 @@ bitcoinRPC = bitcoinProxy() zcashRPC = zcashProxy() def is_myaddr(address): - if address[:1] == 'm': + if address[:1] is 'm' or address[:1] is '1': status = bitcoinRPC.validateaddress(address) else: status = zcashRPC.validateaddress(address) @@ -21,11 +21,16 @@ def is_myaddr(address): # print("Address {0} is mine: {1}".format(address, status)) return status +addr = '1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2' +print("Is myaddr", is_myaddr(addr)) + def find_secret_from_fundtx(currency, p2sh, fundtx): - if currency == 'bitcoin': + if currency is 'bitcoin': secret = bitcoinRPC.find_secret(p2sh, fundtx) - else: + else if currency is 'zcash': secret = zcashRPC.find_secret(p2sh, fundtx) + else: + raise ValueError("Currency not recognized: ", currency) return secret def import_addrs(trade): @@ -33,20 +38,24 @@ def import_addrs(trade): check_fund_status(trade.buy.currency, trade.buy.p2sh) def check_p2sh(currency, address): - if currency == 'bitcoin': + if currency is 'bitcoin': print("Checking funds in Bitcoin p2sh") return bitcoinRPC.check_funds(address) - else: + else if currency is 'zcash': print("Checking funds in Zcash p2sh") return zcashRPC.check_funds(address) + else: + raise ValueError("Currency not recognized: ", currency) def check_fund_status(currency, address): - if currency == 'bitcoin': + if currency is 'bitcoin': print("Checking funds in Bitcoin p2sh") return bitcoinRPC.get_fund_status(address) - else: + else if currency is 'zcash': print("Checking funds in Zcash p2sh") return zcashRPC.get_fund_status(address) + else: + raise ValueError("Currency not recognized: ", currency) # TODO: function to calculate appropriate locktimes between chains # def verify_p2sh(trade): @@ -59,19 +68,42 @@ def check_fund_status(currency, address): # print("Compiled p2sh for htlc does not match what seller sent.") def create_htlc(currency, funder, redeemer, commitment, locktime): - if currency == 'bitcoin': + if currency is 'bitcoin': sell_p2sh = bitcoinRPC.hashtimelockcontract(funder, redeemer, commitment, locktime) - else: + else if currency is 'zcash': sell_p2sh = zcashRPC.hashtimelockcontract(funder, redeemer, commitment, locktime) + else: + raise ValueError("Currency not recognized: ", currency) return sell_p2sh def fund_htlc(currency, p2sh, amount): - if currency == 'bitcoin': + if currency is 'bitcoin': txid = bitcoinRPC.fund_htlc(p2sh, amount) - else: + else if currency is 'zcash': txid = zcashRPC.fund_htlc(p2sh, amount) + else: + raise ValueError("Currency not recognized: ", currency) return txid +def redeem_p2sh(contract, secret): + currency = contract.currency + if currency is 'bitcoin': + res = bitcoinRPC.redeem_contract(contract, secret) + else if currency is 'zcash': + res = zcashRPC.redeem_contract(contract, secret) + else: + raise ValueError("Currency not recognized: ", currency) + return res + +def parse_secret(currency, txid): + if currency is 'bitcoin': + secret = bitcoinRPC.parse_secret(txid) + else if currency is 'zcash': + secret = zcashRPC.parse_secret(txid) + else: + raise ValueError("Currency not recognized: ", currency) + return secret + def fund_contract(contract): txid = fund_htlc(contract.currency, contract.p2sh, contract.amount) return txid @@ -109,21 +141,6 @@ def create_buy_p2sh(trade, commitment, locktime): 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 def buyer_redeem(trade): userInput.authorize_buyer_redeem(trade) From 3fd724f6fc9d34ac540dd81b6927922e429b1e44 Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Tue, 29 Aug 2017 17:17:28 -0700 Subject: [PATCH 02/10] Add logging debug option --- xcat/bitcoinRPC.py | 4 ++++ xcat/cli.py | 25 ++++++++++++++++------ xcat/protocol.py | 52 ++++++++++++++++++++++++++-------------------- xcat/userInput.py | 1 + xcat/xcatconf.py | 6 ++++-- 5 files changed, 57 insertions(+), 31 deletions(-) diff --git a/xcat/bitcoinRPC.py b/xcat/bitcoinRPC.py index 487e3a1..c7eb6b9 100644 --- a/xcat/bitcoinRPC.py +++ b/xcat/bitcoinRPC.py @@ -15,11 +15,15 @@ from bitcoin.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH from bitcoin.wallet import CBitcoinAddress, CBitcoinSecret, P2SHBitcoinAddress, P2PKHBitcoinAddress from xcat.utils import * +import logging FEE = 0.001*COIN class bitcoinProxy(): 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.timeout = timeout diff --git a/xcat/cli.py b/xcat/cli.py index 5b41fc1..965feaa 100644 --- a/xcat/cli.py +++ b/xcat/cli.py @@ -31,7 +31,6 @@ def checkSellStatus(tradeid): trade.buy.redeem_tx = txs['refund_tx'] print("Refund tx: ", txs['refund_tx']) save_state(trade, tradeid) - # Remove from db? Or just from temporary file storage cleanup(tradeid) 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)) @@ -45,6 +44,7 @@ def buyer_check_status(trade): return 'sellerFunded' # step1 # TODO: Find funding txid. How does buyer get seller redeemed tx? elif sellState == 'funded' and hasattr(trade.buy, 'fund_tx'): + print("Seller redeemed") return 'sellerRedeemed' # step3 elif sellState == 'funded' and buyState == 'funded': return 'buyerFunded' # step2 @@ -173,9 +173,9 @@ def checktrade(tradeid): def newtrade(tradeid, **kwargs): print("Creating new XCAT trade...") erase_trade() - tradeid, trade= initialize_trade(tradeid, conf=kwargs['conf']) - print("Trade", trade) - trade = seller_init(tradeid, trade) + tradeid, trade= initialize_trade(tradeid, conf=kwargs['conf'], network=kwargs['network']) + print("New trade created: {0}".format(trade)) + trade = seller_init(tradeid, trade, network=kwargs['network']) print("\nUse 'xcat exporttrade [tradeid]' to export the trade and sent to the buyer.\n") save_state(trade, tradeid) return trade @@ -199,12 +199,24 @@ def main(): ''')) parser.add_argument("command", action="store", help="list commands") 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("-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("--daemon", "-d", action="store_true", help="Run as daemon process") 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 if command == 'importtrade': if args.wormhole: @@ -234,9 +246,9 @@ def main(): if len(args.arguments) < 1: throw("Usage: newtrade [tradeid]") tradeid = args.arguments[0] if args.conf == None: - newtrade(tradeid, network=args.network, conf='cli') + newtrade(tradeid, network=NETWORK, conf='cli') else: - newtrade(tradeid, network=args.network, conf=args.conf) + newtrade(tradeid, network=NETWORK, conf=args.conf) elif command == "daemon": #TODO: not implemented print("Run as daemon process") @@ -248,6 +260,7 @@ def main(): tradeid = args.arguments[0] checkBuyStatus(tradeid) elif command == "step3": + generate(11) tradeid = args.arguments[0] checkSellStatus(tradeid) elif command == "step4": diff --git a/xcat/protocol.py b/xcat/protocol.py index de41c24..a25332b 100644 --- a/xcat/protocol.py +++ b/xcat/protocol.py @@ -8,26 +8,32 @@ import xcat.db as db from xcat.xcatconf import * from xcat.bitcoinRPC import bitcoinProxy from xcat.zcashRPC import zcashProxy +import logging bitcoinRPC = bitcoinProxy() zcashRPC = zcashProxy() +def generate(num): + bitcoinRPC.generate(num) + zcashRPC.generate(num) + def is_myaddr(address): - if address[:1] is 'm' or address[:1] is '1': + # Handle different network prefixes + if address[:1] == 'm': status = bitcoinRPC.validateaddress(address) else: status = zcashRPC.validateaddress(address) - status = status['ismine'] - # print("Address {0} is mine: {1}".format(address, status)) + logging.debug("Address status: ", status) + if status['isvalid'] is False: + raise ValueError("Invalid address: %s" % address) + elif 'ismine' in status: + status = status['ismine'] return status -addr = '1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2' -print("Is myaddr", is_myaddr(addr)) - def find_secret_from_fundtx(currency, p2sh, fundtx): - if currency is 'bitcoin': + if currency == 'bitcoin': secret = bitcoinRPC.find_secret(p2sh, fundtx) - else if currency is 'zcash': + elif currency == 'zcash': secret = zcashRPC.find_secret(p2sh, fundtx) else: raise ValueError("Currency not recognized: ", currency) @@ -38,20 +44,20 @@ def import_addrs(trade): check_fund_status(trade.buy.currency, trade.buy.p2sh) def check_p2sh(currency, address): - if currency is 'bitcoin': + if currency == 'bitcoin': print("Checking funds in Bitcoin p2sh") return bitcoinRPC.check_funds(address) - else if currency is 'zcash': + elif currency == 'zcash': print("Checking funds in Zcash p2sh") return zcashRPC.check_funds(address) else: raise ValueError("Currency not recognized: ", currency) def check_fund_status(currency, address): - if currency is 'bitcoin': + if currency == 'bitcoin': print("Checking funds in Bitcoin p2sh") return bitcoinRPC.get_fund_status(address) - else if currency is 'zcash': + elif currency == 'zcash': print("Checking funds in Zcash p2sh") return zcashRPC.get_fund_status(address) else: @@ -68,18 +74,18 @@ def check_fund_status(currency, address): # print("Compiled p2sh for htlc does not match what seller sent.") def create_htlc(currency, funder, redeemer, commitment, locktime): - if currency is 'bitcoin': + if currency == 'bitcoin': sell_p2sh = bitcoinRPC.hashtimelockcontract(funder, redeemer, commitment, locktime) - else if currency is 'zcash': + elif currency == 'zcash': sell_p2sh = zcashRPC.hashtimelockcontract(funder, redeemer, commitment, locktime) else: raise ValueError("Currency not recognized: ", currency) return sell_p2sh def fund_htlc(currency, p2sh, amount): - if currency is 'bitcoin': + if currency == 'bitcoin': txid = bitcoinRPC.fund_htlc(p2sh, amount) - else if currency is 'zcash': + elif currency == 'zcash': txid = zcashRPC.fund_htlc(p2sh, amount) else: raise ValueError("Currency not recognized: ", currency) @@ -87,18 +93,18 @@ def fund_htlc(currency, p2sh, amount): def redeem_p2sh(contract, secret): currency = contract.currency - if currency is 'bitcoin': + if currency == 'bitcoin': res = bitcoinRPC.redeem_contract(contract, secret) - else if currency is 'zcash': + elif currency == 'zcash': res = zcashRPC.redeem_contract(contract, secret) else: raise ValueError("Currency not recognized: ", currency) return res def parse_secret(currency, txid): - if currency is 'bitcoin': + if currency == 'bitcoin': secret = bitcoinRPC.parse_secret(txid) - else if currency is 'zcash': + elif currency == 'zcash': secret = zcashRPC.parse_secret(txid) else: raise ValueError("Currency not recognized: ", currency) @@ -221,10 +227,10 @@ def initialize_trade(tradeid, **kwargs): return tradeid, trade -def seller_init(tradeid, trade): +def seller_init(tradeid, trade, network): secret = generate_password() 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) # TODO: Implement locktimes and mock block passage of time @@ -237,5 +243,5 @@ def seller_init(tradeid, trade): create_buy_p2sh(trade, hash_of_secret, buy_locktime) trade.commitment = b2x(hash_of_secret) - print("TRADE after seller init", trade.toJSON()) + print("TRADE after seller init: {0}".format(trade.toJSON())) return trade diff --git a/xcat/userInput.py b/xcat/userInput.py index fdcb2cb..e5d0aeb 100644 --- a/xcat/userInput.py +++ b/xcat/userInput.py @@ -2,6 +2,7 @@ from xcat.utils import * from xcat.db import * from xcat.bitcoinRPC import bitcoinProxy from xcat.zcashRPC import zcashProxy +from xcat.xcatconf import * def enter_trade_id(): tradeid = input("Enter a unique identifier for this trade: ") diff --git a/xcat/xcatconf.py b/xcat/xcatconf.py index 6efb053..6c77cab 100644 --- a/xcat/xcatconf.py +++ b/xcat/xcatconf.py @@ -17,9 +17,11 @@ ADDRS = { "zcash": "tmTF7LMLjvEsGdcepWPUsh4vgJNrKMWwEyc" }, "fulfiller": { - "bitcoin": "mm2smEJjRN4xoijEfpb5XvYd8e3EYWezom", - "zcash": "tmPwPdceaJAHQn7UiRCVnJ5tXBXHVqWMkis" + "bitcoin": "mn2boR7rYq9DaAWWrVN5MazHKFyf7UhdyU", + "zcash": "tmErB22A1G74aq32aAh5AoqgQSJsAAAdT2p" }, "amounts": {'buy': {'currency': 'zcash', 'amount': 0.02}, 'sell': {'currency': 'bitcoin', 'amount': 0.01}} } } + +NETWORK = 'testnet' From e6768147c6480ce5f732637e5d8018f3b89f8103 Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Wed, 30 Aug 2017 12:14:32 -0700 Subject: [PATCH 03/10] Refactor redeem and refund --- xcat/bitcoinRPC.py | 67 +++++++++++++++++++++++++------------------- xcat/zcashRPC.py | 69 +++++++++++++++++++++++++--------------------- 2 files changed, 75 insertions(+), 61 deletions(-) diff --git a/xcat/bitcoinRPC.py b/xcat/bitcoinRPC.py index c7eb6b9..d614cf5 100644 --- a/xcat/bitcoinRPC.py +++ b/xcat/bitcoinRPC.py @@ -165,38 +165,47 @@ class bitcoinProxy(): blockcount = self.bitcoind.getblockcount() print("\nCurrent blocknum at time of redeem on Zcash:", blockcount) if blockcount < int(redeemblocknum): - 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: 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} + return self.redeem(contract, fundtx, secret) else: print("nLocktime exceeded, refunding") - print('refundPubKey', refundPubKey) - 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} + return self.refund(contract) else: print("No contract for this p2sh found in database", p2sh) + def redeem(self, 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: 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} + + def refund(self, contract): + fundtx = self.find_transaction_to_address(contract.p2sh) + # Refund self on other chain + refundPubKey = self.find_refundAddr(contract) + logging.debug('refundPubKey: {0}'.format(refundPubKey)) + txid = self.bitcoind.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} + def parse_script(self, script_hex): redeemScript = self.bitcoind.call('decodescript', script_hex) scriptarray = redeemScript['asm'].split(' ') @@ -214,7 +223,7 @@ class bitcoinProxy(): return redeemAddr def find_refundAddr(self, contract): - scriptarray = parse_script(contract.redeemScript) + scriptarray = self.parse_script(contract.redeemScript) funder = scriptarray[13] refundAddr = P2PKHBitcoinAddress.from_bytes(x(funder)) return refundAddr @@ -224,7 +233,7 @@ class bitcoinProxy(): txs = self.bitcoind.listunspent() for tx in txs: if tx['address'] == CBitcoinAddress(p2sh): - print("Found tx to p2sh", p2sh) + logging.debug("Found tx to p2sh: {0}".format(p2sh)) return tx def new_bitcoin_addr(self): diff --git a/xcat/zcashRPC.py b/xcat/zcashRPC.py index 2baefd9..f47af41 100644 --- a/xcat/zcashRPC.py +++ b/xcat/zcashRPC.py @@ -136,48 +136,53 @@ class zcashProxy(): p2sh = P2SHBitcoinAddress(p2sh) if fundtx['address'] == p2sh: print("Found {0} in p2sh {1}, redeeming...".format(amount, p2sh)) - # Where can you find redeemblocknum in the transaction? # redeemblocknum = find_redeemblocknum(contract) blockcount = self.zcashd.getblockcount() print("\nCurrent blocknum at time of redeem on Zcash:", blockcount) if blockcount < contract.redeemblocknum: - # 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} + return self.redeem(contract, fundtx, secret) else: print("nLocktime exceeded, refunding") - refundPubKey = self.find_refundAddr(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} + 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. + 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) + # Refund self on other chain + refundPubKey = self.find_refundAddr(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} + def parse_script(self, script_hex): redeemScript = self.zcashd.decodescript(script_hex) scriptarray = redeemScript['asm'].split(' ') From 2436f0066b732f8ddf310e986c68fca41311a91d Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Wed, 30 Aug 2017 12:14:42 -0700 Subject: [PATCH 04/10] Auto refund --- xcat/cli.py | 9 ++++++--- xcat/protocol.py | 12 ++++++++++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/xcat/cli.py b/xcat/cli.py index 965feaa..3b4970a 100644 --- a/xcat/cli.py +++ b/xcat/cli.py @@ -27,9 +27,11 @@ def checkSellStatus(tradeid): if 'redeem_tx' in txs: trade.buy.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'] - 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) cleanup(tradeid) elif status == 'sellerFunded': @@ -44,7 +46,6 @@ def buyer_check_status(trade): return 'sellerFunded' # step1 # TODO: Find funding txid. How does buyer get seller redeemed tx? elif sellState == 'funded' and hasattr(trade.buy, 'fund_tx'): - print("Seller redeemed") return 'sellerRedeemed' # step3 elif sellState == 'funded' and buyState == 'funded': return 'buyerFunded' # step2 @@ -101,6 +102,7 @@ def checkBuyStatus(tradeid): save_state(trade, tradeid) print("XCAT trade complete!") else: + # Search if tx has been refunded from p2sh print("Secret not found in redeemtx") # Import a trade in hex, and save to db @@ -264,5 +266,6 @@ def main(): tradeid = args.arguments[0] checkSellStatus(tradeid) elif command == "step4": + generate(1) tradeid = args.arguments[0] checkBuyStatus(tradeid) diff --git a/xcat/protocol.py b/xcat/protocol.py index a25332b..7f0281c 100644 --- a/xcat/protocol.py +++ b/xcat/protocol.py @@ -101,6 +101,16 @@ def redeem_p2sh(contract, secret): 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) @@ -171,7 +181,6 @@ def buyer_redeem(trade): def seller_redeem_p2sh(trade, secret): buy = trade.buy userInput.authorize_seller_redeem(buy) - if trade.sell.get_status() == 'redeemed': print("You already redeemed the funds and acquired {0} {1}".format(buy.amount, buy.currency)) exit() @@ -226,7 +235,6 @@ def initialize_trade(tradeid, **kwargs): print(trade.buy.__dict__) return tradeid, trade - def seller_init(tradeid, trade, network): secret = generate_password() db.save_secret(tradeid, secret) From 16826db42bd9991e1ea6c727a1d1e5d3f4d47e9e Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Wed, 30 Aug 2017 17:32:00 -0700 Subject: [PATCH 05/10] Modify parse secret to handle refund tx without secret --- xcat/zcashRPC.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/xcat/zcashRPC.py b/xcat/zcashRPC.py index f47af41..91f7245 100644 --- a/xcat/zcashRPC.py +++ b/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.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH from zcash.wallet import CBitcoinAddress, CBitcoinSecret, P2SHBitcoinAddress, P2PKHBitcoinAddress +import logging from xcat.utils import x2s @@ -107,9 +108,16 @@ class zcashProxy(): if('txid' in decoded['vin'][0]): sendid = decoded['vin'][0]['txid'] if (sendid == fundtx_input ): - print("Found funding tx: ", sendid) - return self.parse_secret(lx(tx['txid'])) - print("Redeem transaction with secret not found") + 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): @@ -118,9 +126,13 @@ class zcashProxy(): scriptSig = decoded['vin'][0]['scriptSig'] asm = scriptSig['asm'].split(" ") pubkey = asm[1] - secret = x2s(asm[2]) + try: + secret = x2s(asm[2]) + except: + secret = None redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey)) - return secret + print("redeemPubkey: ", redeemPubkey) + return secret, redeemPubkey def redeem_contract(self, contract, secret): # How to find redeemScript and redeemblocknum from blockchain? From 59c2a1c37be7aa39809b936d1fe9d1136911d81b Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Fri, 8 Sep 2017 14:24:12 -0700 Subject: [PATCH 06/10] Debug checklocktimeverify process, not complete --- xcat/bitcoinRPC.py | 50 +++++++++++++++------- xcat/checklocktimeverify.py | 83 +++++++++++++++++++++++++++++++++++++ xcat/cli.py | 4 +- xcat/zcashRPC.py | 44 ++++++++++++++------ 4 files changed, 152 insertions(+), 29 deletions(-) create mode 100644 xcat/checklocktimeverify.py diff --git a/xcat/bitcoinRPC.py b/xcat/bitcoinRPC.py index d614cf5..3b880a1 100644 --- a/xcat/bitcoinRPC.py +++ b/xcat/bitcoinRPC.py @@ -10,7 +10,7 @@ import bitcoin.rpc from bitcoin import SelectParams from bitcoin.core import b2x, lx, b2lx, x, COIN, COutPoint, CMutableTxOut, CMutableTxIn, CMutableTransaction, Hash160, CTransaction 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.wallet import CBitcoinAddress, CBitcoinSecret, P2SHBitcoinAddress, P2PKHBitcoinAddress @@ -147,7 +147,7 @@ class bitcoinProxy(): print("Parsing script for redeem_contract...") scriptarray = self.parse_script(contract.redeemScript) 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])) p2sh = contract.p2sh #checking there are funds in the address @@ -172,22 +172,23 @@ class bitcoinProxy(): else: print("No contract for this p2sh found in database", p2sh) - def redeem(self, contract): - print('redeemPubKey', redeemPubKey) - zec_redeemScript = CScript(x(contract.redeemScript)) + 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, redeemPubKey.to_scriptPubKey()) + txout = CMutableTxOut(fundtx['amount'] - FEE, self.redeemPubKey.to_scriptPubKey()) # Create the unsigned raw transaction. 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 - privkey = self.bitcoind.dumpprivkey(redeemPubKey) + 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, zec_redeemScript]) + txin.scriptSig = CScript([sig, privkey.pub, preimage, OP_TRUE, redeemScript]) # 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())) VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,)) print("Script verified, sending raw transaction...") @@ -198,11 +199,32 @@ class bitcoinProxy(): def refund(self, contract): fundtx = self.find_transaction_to_address(contract.p2sh) - # Refund self on other chain + print("Fund tx found in refund: ", fundtx) refundPubKey = self.find_refundAddr(contract) - logging.debug('refundPubKey: {0}'.format(refundPubKey)) - txid = self.bitcoind.sendtoaddress(refundPubKey, fundtx['amount'] - FEE) - refund_tx = b2x(lx(b2x(txid))) + 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]) + tx.nLockTime = 2430 + sighash = SignatureHash(redeemScript, tx, 0, SIGHASH_ALL) + privkey = self.bitcoind.dumpprivkey(refundPubKey) + sig = privkey.sign(sighash) + bytes([SIGHASH_ALL]) + # Sign without secret + # OP_IF, OP_SHA256, commitment, OP_EQUALVERIFY,OP_DUP, OP_HASH160, redeemerAddr, + # OP_ELSE, redeemblocknum, OP_CHECKLOCKTIMEVERIFY, OP_DROP, OP_DUP, OP_HASH160, funderAddr, + # OP_ENDIF, + # OP_EQUALVERIFY, OP_CHECKSIG + 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} diff --git a/xcat/checklocktimeverify.py b/xcat/checklocktimeverify.py new file mode 100644 index 0000000..72a8bfd --- /dev/null +++ b/xcat/checklocktimeverify.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 + +import sys +if sys.version_info.major < 3: + sys.stderr.write('Sorry, Python 3.x required by this example.\n') + sys.exit(1) + +import bitcoin +import bitcoin.rpc +from bitcoin import SelectParams +from bitcoin.core import b2x, lx, b2lx, x, COIN, COutPoint, CMutableTxOut, CMutableTxIn, CMutableTransaction, Hash160, CTransaction +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, OP_FALSE +from bitcoin.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH +from bitcoin.wallet import CBitcoinAddress, CBitcoinSecret, P2SHBitcoinAddress, P2PKHBitcoinAddress + +from xcat.utils import * +import logging + +FEE = 0.001*COIN + +SelectParams('regtest') +bitcoind = bitcoin.rpc.Proxy() + +# Simple CLTV test p2sh +blocknum = bitcoind.getblockcount() +print("Current blocknum on Bitcoin: ", blocknum) +redeemblocknum = blocknum + 1 +print("Redeemblocknum on Bitcoin: ", redeemblocknum) +redeemScript = CScript([redeemblocknum, OP_CHECKLOCKTIMEVERIFY]) +print("Redeem script for p2sh contract on Bitcoin blockchain: {0}".format(b2x(redeemScript))) +txin_scriptPubKey = redeemScript.to_p2sh_scriptPubKey() +# Convert the P2SH scriptPubKey to a base58 Bitcoin address +txin_p2sh_address = CBitcoinAddress.from_scriptPubKey(txin_scriptPubKey) +p2sh = str(txin_p2sh_address) +# Import address at same time you create +bitcoind.importaddress(p2sh, "", False) + +bitcoind.generate(3) +blocknum = bitcoind.getblockcount() +print("Current blocknum on Bitcoin 2: ", blocknum) +print("Redeemblocknum on Bitcoin 2: ", redeemblocknum) + +send_amount = float(0.01) * COIN +# Import address at same time that you fund it +bitcoind.importaddress(p2sh, "", False) +fund_txid = bitcoind.sendtoaddress(p2sh, send_amount) +txid = b2x(lx(b2x(fund_txid))) +print("fund txid", txid) + +# Find the fund tx... +txs = bitcoind.listunspent() +for tx in txs: + if tx['address'] == CBitcoinAddress(p2sh): + print("Found tx to p2sh: {0}".format(p2sh)) + fundtx = tx + +# redeemScript = CScript(x(redeemScript)) +txin = CMutableTxIn(fundtx['outpoint']) + +refundAddr = CBitcoinAddress('mvc56qCEVj6p57xZ5URNC3v7qbatudHQ9b') +txout = CMutableTxOut(fundtx['amount'] - FEE, refundAddr.to_scriptPubKey()) +# Create the unsigned raw transaction. +tx = CMutableTransaction([txin], [txout]) +# tx.nLockTime = 2430 +sighash = SignatureHash(redeemScript, tx, 0, SIGHASH_ALL) + +# privkey = bitcoind.dumpprivkey(refundPubKey) +# sig = privkey.sign(sighash) + bytes([SIGHASH_ALL]) +# Sign without secret +# OP_IF, OP_SHA256, commitment, OP_EQUALVERIFY,OP_DUP, OP_HASH160, redeemerAddr, +# OP_ELSE, redeemblocknum, OP_CHECKLOCKTIMEVERIFY, OP_DROP, OP_DUP, OP_HASH160, funderAddr, +# OP_ENDIF, +# OP_EQUALVERIFY, OP_CHECKSIG +txin.scriptSig = CScript([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... ", res) +txid = bitcoind.sendrawtransaction(tx) +refund_tx = b2x(lx(b2x(txid))) diff --git a/xcat/cli.py b/xcat/cli.py index 3b4970a..ea2b440 100644 --- a/xcat/cli.py +++ b/xcat/cli.py @@ -262,10 +262,10 @@ def main(): tradeid = args.arguments[0] checkBuyStatus(tradeid) elif command == "step3": - generate(11) + generate(31) tradeid = args.arguments[0] checkSellStatus(tradeid) elif command == "step4": - generate(1) + # generate(1) tradeid = args.arguments[0] checkBuyStatus(tradeid) diff --git a/xcat/zcashRPC.py b/xcat/zcashRPC.py index 91f7245..6d91fc7 100644 --- a/xcat/zcashRPC.py +++ b/xcat/zcashRPC.py @@ -105,21 +105,39 @@ class zcashProxy(): for tx in txs: raw = self.zcashd.gettransaction(lx(tx['txid']))['hex'] decoded = self.zcashd.decoderawtransaction(raw) + # print("TXINFO", decoded['vin'][0]) if('txid' in decoded['vin'][0]): sendid = decoded['vin'][0]['txid'] 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") + print("Found funding tx: ", sendid) + return self.parse_secret(lx(tx['txid'])) + print("Redeem transaction with secret not found") 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): raw = self.zcashd.gettransaction(txid, True)['hex'] decoded = self.zcashd.decoderawtransaction(raw) @@ -130,9 +148,9 @@ class zcashProxy(): secret = x2s(asm[2]) except: secret = None - redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey)) - print("redeemPubkey: ", redeemPubkey) - return secret, redeemPubkey + self.redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey)) + print("redeemPubkey: ", self.redeemPubkey) + return secret def redeem_contract(self, contract, secret): # How to find redeemScript and redeemblocknum from blockchain? From 592e26453c74b1ee38ba849cb287b97b9ba2b56a Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Fri, 8 Sep 2017 14:43:32 -0700 Subject: [PATCH 07/10] sighash anyonecanpay --- xcat/checklocktimeverify.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xcat/checklocktimeverify.py b/xcat/checklocktimeverify.py index 72a8bfd..336e3b9 100644 --- a/xcat/checklocktimeverify.py +++ b/xcat/checklocktimeverify.py @@ -10,7 +10,7 @@ import bitcoin.rpc from bitcoin import SelectParams from bitcoin.core import b2x, lx, b2lx, x, COIN, COutPoint, CMutableTxOut, CMutableTxIn, CMutableTransaction, Hash160, CTransaction 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, OP_FALSE +from bitcoin.core.script import CScript, OP_DUP, OP_IF, OP_ELSE, OP_ENDIF, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG, SignatureHash, SIGHASH_ALL, SIGHASH_ANYONECANPAY, OP_FALSE, OP_DROP, OP_CHECKLOCKTIMEVERIFY, OP_SHA256, OP_TRUE, OP_FALSE from bitcoin.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH from bitcoin.wallet import CBitcoinAddress, CBitcoinSecret, P2SHBitcoinAddress, P2PKHBitcoinAddress @@ -63,7 +63,7 @@ txout = CMutableTxOut(fundtx['amount'] - FEE, refundAddr.to_scriptPubKey()) # Create the unsigned raw transaction. tx = CMutableTransaction([txin], [txout]) # tx.nLockTime = 2430 -sighash = SignatureHash(redeemScript, tx, 0, SIGHASH_ALL) +sighash = SignatureHash(redeemScript, tx, 0, SIGHASH_ANYONECANPAY) # privkey = bitcoind.dumpprivkey(refundPubKey) # sig = privkey.sign(sighash) + bytes([SIGHASH_ALL]) From a54b42c645bfa45dce4772970cc44f6c6c14d661 Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Mon, 11 Sep 2017 11:43:04 -0700 Subject: [PATCH 08/10] sighash --- xcat/checklocktimeverify.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/xcat/checklocktimeverify.py b/xcat/checklocktimeverify.py index 336e3b9..7e88a53 100644 --- a/xcat/checklocktimeverify.py +++ b/xcat/checklocktimeverify.py @@ -10,7 +10,7 @@ import bitcoin.rpc from bitcoin import SelectParams from bitcoin.core import b2x, lx, b2lx, x, COIN, COutPoint, CMutableTxOut, CMutableTxIn, CMutableTransaction, Hash160, CTransaction 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, SIGHASH_ANYONECANPAY, OP_FALSE, OP_DROP, OP_CHECKLOCKTIMEVERIFY, OP_SHA256, OP_TRUE, OP_FALSE +from bitcoin.core.script import CScript, OP_DUP, OP_IF, OP_ELSE, OP_ENDIF, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG, SignatureHash, SIGHASH_ALL, SIGHASH_NONE, SIGHASH_ANYONECANPAY, OP_FALSE, OP_DROP, OP_CHECKLOCKTIMEVERIFY, OP_SHA256, OP_TRUE, OP_FALSE from bitcoin.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH from bitcoin.wallet import CBitcoinAddress, CBitcoinSecret, P2SHBitcoinAddress, P2PKHBitcoinAddress @@ -22,12 +22,19 @@ FEE = 0.001*COIN SelectParams('regtest') bitcoind = bitcoin.rpc.Proxy() +address = bitcoind.getnewaddress() +print(address) +privkey = bitcoind.dumpprivkey(address) + # Simple CLTV test p2sh blocknum = bitcoind.getblockcount() print("Current blocknum on Bitcoin: ", blocknum) redeemblocknum = blocknum + 1 print("Redeemblocknum on Bitcoin: ", redeemblocknum) + +# Script redeemScript = CScript([redeemblocknum, OP_CHECKLOCKTIMEVERIFY]) + print("Redeem script for p2sh contract on Bitcoin blockchain: {0}".format(b2x(redeemScript))) txin_scriptPubKey = redeemScript.to_p2sh_scriptPubKey() # Convert the P2SH scriptPubKey to a base58 Bitcoin address @@ -63,16 +70,18 @@ txout = CMutableTxOut(fundtx['amount'] - FEE, refundAddr.to_scriptPubKey()) # Create the unsigned raw transaction. tx = CMutableTransaction([txin], [txout]) # tx.nLockTime = 2430 -sighash = SignatureHash(redeemScript, tx, 0, SIGHASH_ANYONECANPAY) - +sighash = bytes(SignatureHash(redeemScript, tx, 0, SIGHASH_NONE)) # privkey = bitcoind.dumpprivkey(refundPubKey) -# sig = privkey.sign(sighash) + bytes([SIGHASH_ALL]) +# sig = privkey.sign(sighash) + bytes([SIGHASH_NONE]) +sig = privkey.sign(sighash) + bytes([SIGHASH_NONE]) +signed_scriptSig = CScript([sig] + list([SIGHASH_NONE])) + # Sign without secret # OP_IF, OP_SHA256, commitment, OP_EQUALVERIFY,OP_DUP, OP_HASH160, redeemerAddr, # OP_ELSE, redeemblocknum, OP_CHECKLOCKTIMEVERIFY, OP_DROP, OP_DUP, OP_HASH160, funderAddr, # OP_ENDIF, # OP_EQUALVERIFY, OP_CHECKSIG -txin.scriptSig = CScript([redeemScript]) +txin.scriptSig = CScript([signed_scriptSig, redeemScript]) # txin.nSequence = 2185 txin_scriptPubKey = redeemScript.to_p2sh_scriptPubKey() print('Raw redeem transaction hex: {0}'.format(b2x(tx.serialize()))) From 63d776bf364a9a8e17a543209d2010140ac27307 Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Mon, 11 Sep 2017 11:44:54 -0700 Subject: [PATCH 09/10] debug --- xcat/bitcoinRPC.py | 4 -- xcat/checklocktimeverify.py | 92 ------------------------------------- xcat/zcashRPC.py | 25 ++++++++-- 3 files changed, 21 insertions(+), 100 deletions(-) delete mode 100644 xcat/checklocktimeverify.py diff --git a/xcat/bitcoinRPC.py b/xcat/bitcoinRPC.py index 3b880a1..778cead 100644 --- a/xcat/bitcoinRPC.py +++ b/xcat/bitcoinRPC.py @@ -213,10 +213,6 @@ class bitcoinProxy(): privkey = self.bitcoind.dumpprivkey(refundPubKey) sig = privkey.sign(sighash) + bytes([SIGHASH_ALL]) # Sign without secret - # OP_IF, OP_SHA256, commitment, OP_EQUALVERIFY,OP_DUP, OP_HASH160, redeemerAddr, - # OP_ELSE, redeemblocknum, OP_CHECKLOCKTIMEVERIFY, OP_DROP, OP_DUP, OP_HASH160, funderAddr, - # OP_ENDIF, - # OP_EQUALVERIFY, OP_CHECKSIG txin.scriptSig = CScript([sig, privkey.pub, OP_FALSE, redeemScript]) # txin.nSequence = 2185 txin_scriptPubKey = redeemScript.to_p2sh_scriptPubKey() diff --git a/xcat/checklocktimeverify.py b/xcat/checklocktimeverify.py deleted file mode 100644 index 7e88a53..0000000 --- a/xcat/checklocktimeverify.py +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env python3 - -import sys -if sys.version_info.major < 3: - sys.stderr.write('Sorry, Python 3.x required by this example.\n') - sys.exit(1) - -import bitcoin -import bitcoin.rpc -from bitcoin import SelectParams -from bitcoin.core import b2x, lx, b2lx, x, COIN, COutPoint, CMutableTxOut, CMutableTxIn, CMutableTransaction, Hash160, CTransaction -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, SIGHASH_NONE, SIGHASH_ANYONECANPAY, OP_FALSE, OP_DROP, OP_CHECKLOCKTIMEVERIFY, OP_SHA256, OP_TRUE, OP_FALSE -from bitcoin.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH -from bitcoin.wallet import CBitcoinAddress, CBitcoinSecret, P2SHBitcoinAddress, P2PKHBitcoinAddress - -from xcat.utils import * -import logging - -FEE = 0.001*COIN - -SelectParams('regtest') -bitcoind = bitcoin.rpc.Proxy() - -address = bitcoind.getnewaddress() -print(address) -privkey = bitcoind.dumpprivkey(address) - -# Simple CLTV test p2sh -blocknum = bitcoind.getblockcount() -print("Current blocknum on Bitcoin: ", blocknum) -redeemblocknum = blocknum + 1 -print("Redeemblocknum on Bitcoin: ", redeemblocknum) - -# Script -redeemScript = CScript([redeemblocknum, OP_CHECKLOCKTIMEVERIFY]) - -print("Redeem script for p2sh contract on Bitcoin blockchain: {0}".format(b2x(redeemScript))) -txin_scriptPubKey = redeemScript.to_p2sh_scriptPubKey() -# Convert the P2SH scriptPubKey to a base58 Bitcoin address -txin_p2sh_address = CBitcoinAddress.from_scriptPubKey(txin_scriptPubKey) -p2sh = str(txin_p2sh_address) -# Import address at same time you create -bitcoind.importaddress(p2sh, "", False) - -bitcoind.generate(3) -blocknum = bitcoind.getblockcount() -print("Current blocknum on Bitcoin 2: ", blocknum) -print("Redeemblocknum on Bitcoin 2: ", redeemblocknum) - -send_amount = float(0.01) * COIN -# Import address at same time that you fund it -bitcoind.importaddress(p2sh, "", False) -fund_txid = bitcoind.sendtoaddress(p2sh, send_amount) -txid = b2x(lx(b2x(fund_txid))) -print("fund txid", txid) - -# Find the fund tx... -txs = bitcoind.listunspent() -for tx in txs: - if tx['address'] == CBitcoinAddress(p2sh): - print("Found tx to p2sh: {0}".format(p2sh)) - fundtx = tx - -# redeemScript = CScript(x(redeemScript)) -txin = CMutableTxIn(fundtx['outpoint']) - -refundAddr = CBitcoinAddress('mvc56qCEVj6p57xZ5URNC3v7qbatudHQ9b') -txout = CMutableTxOut(fundtx['amount'] - FEE, refundAddr.to_scriptPubKey()) -# Create the unsigned raw transaction. -tx = CMutableTransaction([txin], [txout]) -# tx.nLockTime = 2430 -sighash = bytes(SignatureHash(redeemScript, tx, 0, SIGHASH_NONE)) -# privkey = bitcoind.dumpprivkey(refundPubKey) -# sig = privkey.sign(sighash) + bytes([SIGHASH_NONE]) -sig = privkey.sign(sighash) + bytes([SIGHASH_NONE]) -signed_scriptSig = CScript([sig] + list([SIGHASH_NONE])) - -# Sign without secret -# OP_IF, OP_SHA256, commitment, OP_EQUALVERIFY,OP_DUP, OP_HASH160, redeemerAddr, -# OP_ELSE, redeemblocknum, OP_CHECKLOCKTIMEVERIFY, OP_DROP, OP_DUP, OP_HASH160, funderAddr, -# OP_ENDIF, -# OP_EQUALVERIFY, OP_CHECKSIG -txin.scriptSig = CScript([signed_scriptSig, 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... ", res) -txid = bitcoind.sendrawtransaction(tx) -refund_tx = b2x(lx(b2x(txid))) diff --git a/xcat/zcashRPC.py b/xcat/zcashRPC.py index 6d91fc7..c5fe978 100644 --- a/xcat/zcashRPC.py +++ b/xcat/zcashRPC.py @@ -205,11 +205,28 @@ class zcashProxy(): def refund(self, contract): fundtx = self.find_transaction_to_address(contract.p2sh) - # Refund self on other chain + print("Fund tx found in refund: ", fundtx) refundPubKey = self.find_refundAddr(contract) - print('refundPubKey: ', refundPubKey) - txid = self.zcashd.sendtoaddress(refundPubKey, fundtx['amount'] - FEE) - refund_tx = b2x(lx(b2x(txid))) + 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]) + tx.nLockTime = 2430 + 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} From 70424735543e7f0fde60646be107c56f1d0f835c Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Mon, 11 Sep 2017 11:52:08 -0700 Subject: [PATCH 10/10] refund script bug --- xcat/bitcoinRPC.py | 1 - xcat/zcashRPC.py | 1 - 2 files changed, 2 deletions(-) diff --git a/xcat/bitcoinRPC.py b/xcat/bitcoinRPC.py index 778cead..338d8e3 100644 --- a/xcat/bitcoinRPC.py +++ b/xcat/bitcoinRPC.py @@ -208,7 +208,6 @@ class bitcoinProxy(): txout = CMutableTxOut(fundtx['amount'] - FEE, refundPubKey.to_scriptPubKey()) # Create the unsigned raw transaction. tx = CMutableTransaction([txin], [txout]) - tx.nLockTime = 2430 sighash = SignatureHash(redeemScript, tx, 0, SIGHASH_ALL) privkey = self.bitcoind.dumpprivkey(refundPubKey) sig = privkey.sign(sighash) + bytes([SIGHASH_ALL]) diff --git a/xcat/zcashRPC.py b/xcat/zcashRPC.py index c5fe978..b2cafd0 100644 --- a/xcat/zcashRPC.py +++ b/xcat/zcashRPC.py @@ -214,7 +214,6 @@ class zcashProxy(): txout = CMutableTxOut(fundtx['amount'] - FEE, refundPubKey.to_scriptPubKey()) # Create the unsigned raw transaction. tx = CMutableTransaction([txin], [txout]) - tx.nLockTime = 2430 sighash = SignatureHash(redeemScript, tx, 0, SIGHASH_ALL) privkey = self.zcashd.dumpprivkey(refundPubKey) sig = privkey.sign(sighash) + bytes([SIGHASH_ALL])