From b41b587aab8fe89e3782af79b943ef5b4910df50 Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Fri, 25 Aug 2017 14:36:42 -0700 Subject: [PATCH 1/3] Refactor bitcoinRPC to initialize new proxies for actions like user input --- xcat/bitcoinRPC.py | 375 +++++++++++++++++++++++---------------------- xcat/db.py | 2 - xcat/protocol.py | 4 +- xcat/userInput.py | 2 + 4 files changed, 195 insertions(+), 188 deletions(-) diff --git a/xcat/bitcoinRPC.py b/xcat/bitcoinRPC.py index b8e638f..2735ba3 100644 --- a/xcat/bitcoinRPC.py +++ b/xcat/bitcoinRPC.py @@ -17,208 +17,213 @@ from bitcoin.wallet import CBitcoinAddress, CBitcoinSecret, P2SHBitcoinAddress, from xcat.utils import * from xcat.zcashRPC import parse_script -SelectParams('testnet') -# SelectParams('regtest') -# TODO: Accurately read user and pw info -bitcoind = bitcoin.rpc.Proxy(timeout=900) FEE = 0.001*COIN -def validateaddress(addr): - return bitcoind.validateaddress(addr) -def find_secret(p2sh, fundtx_input): - txs = bitcoind.call('listtransactions', "*", 20, 0, True) - for tx in txs: - raw = bitcoind.gettransaction(lx(tx['txid']))['hex'] - decoded = bitcoind.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 tx: ", sendid) - return parse_secret(lx(tx['txid'])) - print("Redeem transaction with secret not found") - return +class bitcoinProxy(): + def __init__(self, network='testnet', timeout=900): + self.network = network + self.timeout = timeout -def parse_secret(txid): - raw = zcashd.gettransaction(txid, True)['hex'] - decoded = zcashd.call('decoderawtransaction', raw) - scriptSig = decoded['vin'][0]['scriptSig'] - asm = scriptSig['asm'].split(" ") - pubkey = asm[1] - secret = x2s(asm[2]) - redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey)) - return secret + SelectParams(self.network) + self.bitcoind = bitcoin.rpc.Proxy(timeout=self.timeout) -def get_keys(funder_address, redeemer_address): - fundpubkey = CBitcoinAddress(funder_address) - redeempubkey = CBitcoinAddress(redeemer_address) - # fundpubkey = bitcoind.getnewaddress() - # redeempubkey = bitcoind.getnewaddress() - return fundpubkey, redeempubkey + def validateaddress(self, addr): + return self.bitcoind.validateaddress(addr) -def privkey(address): - bitcoind.dumpprivkey(address) + def find_secret(self, p2sh, fundtx_input): + txs = self.bitcoind.call('listtransactions', "*", 20, 0, True) + for tx in txs: + raw = self.bitcoind.gettransaction(lx(tx['txid']))['hex'] + decoded = self.bitcoind.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 tx: ", sendid) + return parse_secret(lx(tx['txid'])) + print("Redeem transaction with secret not found") + return -def hashtimelockcontract(funder, redeemer, commitment, locktime): - funderAddr = CBitcoinAddress(funder) - redeemerAddr = CBitcoinAddress(redeemer) - if type(commitment) == str: - commitment = x(commitment) - # h = sha256(secret) - blocknum = bitcoind.getblockcount() - print("Current blocknum on Bitcoin: ", blocknum) - redeemblocknum = blocknum + locktime - print("Redeemblocknum on Bitcoin: ", redeemblocknum) - redeemScript = CScript([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]) - # 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) - print("p2sh computed", p2sh) - return {'p2sh': p2sh, 'redeemblocknum': redeemblocknum, 'redeemScript': b2x(redeemScript), 'redeemer': redeemer, 'funder': funder, 'locktime': locktime} + def parse_secret(self, txid): + raw = zcashd.gettransaction(txid, True)['hex'] + decoded = zcashd.call('decoderawtransaction', raw) + scriptSig = decoded['vin'][0]['scriptSig'] + asm = scriptSig['asm'].split(" ") + pubkey = asm[1] + secret = x2s(asm[2]) + redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey)) + return secret -def fund_htlc(p2sh, amount): - send_amount = float(amount) * 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))) - return txid + def get_keys(self, funder_address, redeemer_address): + fundpubkey = CBitcoinAddress(funder_address) + redeempubkey = CBitcoinAddress(redeemer_address) + # fundpubkey = self.bitcoind.getnewaddress() + # redeempubkey = self.bitcoind.getnewaddress() + return fundpubkey, redeempubkey -# Following two functions are about the same -def check_funds(p2sh): - bitcoind.importaddress(p2sh, "", False) - # Get amount in address - amount = bitcoind.getreceivedbyaddress(p2sh, 0) - amount = amount/COIN - return amount + def privkey(self, address): + self.bitcoind.dumpprivkey(address) -def get_fund_status(p2sh): - bitcoind.importaddress(p2sh, "", False) - amount = bitcoind.getreceivedbyaddress(p2sh, 0) - amount = amount/COIN - print("Amount in bitcoin p2sh: ", amount, p2sh) - if amount > 0: - return 'funded' - else: - return 'empty' + def hashtimelockcontract(self, funder, redeemer, commitment, locktime): + funderAddr = CBitcoinAddress(funder) + redeemerAddr = CBitcoinAddress(redeemer) + if type(commitment) == str: + commitment = x(commitment) + # h = sha256(secret) + blocknum = self.bitcoind.getblockcount() + print("Current blocknum on Bitcoin: ", blocknum) + redeemblocknum = blocknum + locktime + print("Redeemblocknum on Bitcoin: ", redeemblocknum) + redeemScript = CScript([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]) + # 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 + self.bitcoind.importaddress(p2sh, "", False) + print("p2sh computed", p2sh) + return {'p2sh': p2sh, 'redeemblocknum': redeemblocknum, 'redeemScript': b2x(redeemScript), 'redeemer': redeemer, 'funder': funder, 'locktime': locktime} -## TODO: FIX search for p2sh in block -def search_p2sh(block, p2sh): - print("Fetching block...") - blockdata = bitcoind.getblock(lx(block)) - print("done fetching block") - txs = blockdata.vtx - print("txs", txs) - for tx in txs: - txhex = b2x(tx.serialize()) - # Using my fork of python-zcashlib to get result of decoderawtransaction - txhex = txhex + '00' - rawtx = zcashd.decoderawtransaction(txhex) - # print('rawtx', rawtx) - print(rawtx) - for vout in rawtx['vout']: - if 'addresses' in vout['scriptPubKey']: - for addr in vout['scriptPubKey']['addresses']: - print("Sent to address:", addr) - if addr == p2sh: - print("Address to p2sh found in transaction!", addr) - print("Returning from search_p2sh") + def fund_htlc(self, p2sh, amount): + send_amount = float(amount) * COIN + # Import address at same time that you fund it + self.bitcoind.importaddress(p2sh, "", False) + fund_txid = self.bitcoind.sendtoaddress(p2sh, send_amount) + txid = b2x(lx(b2x(fund_txid))) + return txid -def get_tx_details(txid): - # must convert txid string to bytes x(txid) - fund_txinfo = bitcoind.gettransaction(lx(txid)) - return fund_txinfo['details'][0] + # Following two functions are about the same + def check_funds(self, p2sh): + self.bitcoind.importaddress(p2sh, "", False) + # Get amount in address + amount = self.bitcoind.getreceivedbyaddress(p2sh, 0) + amount = amount/COIN + return amount -def redeem_contract(contract, secret): - print("Parsing script for redeem_contract...") - scriptarray = parse_script(contract.redeemScript) - redeemblocknum = scriptarray[8] - redeemPubKey = P2PKHBitcoinAddress.from_bytes(x(scriptarray[6])) - refundPubKey = P2PKHBitcoinAddress.from_bytes(x(scriptarray[13])) - p2sh = contract.p2sh - #checking there are funds in the address - amount = check_funds(p2sh) - if(amount == 0): - print("address ", p2sh, " not funded") - quit() - fundtx = find_transaction_to_address(p2sh) - amount = fundtx['amount'] / COIN - # print("Found fund_tx: ", fundtx) - p2sh = P2SHBitcoinAddress(p2sh) - if fundtx['address'] == p2sh: - print("Found {0} in p2sh {1}, redeeming...".format(amount, p2sh)) - - blockcount = 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 = 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 = bitcoind.sendrawtransaction(tx) - fund_tx = str(fundtx['outpoint']) - redeem_tx = b2x(lx(b2x(txid))) - return {"redeem_tx": redeem_tx, "fund_tx": fund_tx} + def get_fund_status(self, p2sh): + self.bitcoind.importaddress(p2sh, "", False) + amount = self.bitcoind.getreceivedbyaddress(p2sh, 0) + amount = amount/COIN + print("Amount in bitcoin p2sh: ", amount, p2sh) + if amount > 0: + return 'funded' else: - print("nLocktime exceeded, refunding") - print('refundPubKey', refundPubKey) - txid = 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: - print("No contract for this p2sh found in database", p2sh) + return 'empty' -def find_redeemblocknum(contract): - scriptarray = parse_script(contract.redeemScript) - redeemblocknum = scriptarray[8] - return int(redeemblocknum) + ## TODO: FIX search for p2sh in block + def search_p2sh(self, block, p2sh): + print("Fetching block...") + blockdata = self.bitcoind.getblock(lx(block)) + print("done fetching block") + txs = blockdata.vtx + print("txs", txs) + for tx in txs: + txhex = b2x(tx.serialize()) + # Using my fork of python-zcashlib to get result of decoderawtransaction + txhex = txhex + '00' + rawtx = zcashd.decoderawtransaction(txhex) + # print('rawtx', rawtx) + print(rawtx) + for vout in rawtx['vout']: + if 'addresses' in vout['scriptPubKey']: + for addr in vout['scriptPubKey']['addresses']: + print("Sent to address:", addr) + if addr == p2sh: + print("Address to p2sh found in transaction!", addr) + print("Returning from search_p2sh") -def find_redeemAddr(contract): - scriptarray = parse_script(contract.redeemScript) - redeemer = scriptarray[6] - redeemAddr = P2PKHBitcoinAddress.from_bytes(x(redeemer)) - return redeemAddr + def get_tx_details(self, txid): + # must convert txid string to bytes x(txid) + fund_txinfo = self.bitcoind.gettransaction(lx(txid)) + return fund_txinfo['details'][0] -def find_refundAddr(contract): - scriptarray = parse_script(contract.redeemScript) - funder = scriptarray[13] - refundAddr = P2PKHBitcoinAddress.from_bytes(x(funder)) - return refundAddr + def redeem_contract(self, contract, secret): + print("Parsing script for redeem_contract...") + scriptarray = parse_script(contract.redeemScript) + redeemblocknum = scriptarray[8] + redeemPubKey = P2PKHBitcoinAddress.from_bytes(x(scriptarray[6])) + refundPubKey = P2PKHBitcoinAddress.from_bytes(x(scriptarray[13])) + p2sh = contract.p2sh + #checking there are funds in the address + amount = check_funds(p2sh) + if(amount == 0): + print("address ", p2sh, " not funded") + quit() + fundtx = find_transaction_to_address(p2sh) + amount = fundtx['amount'] / COIN + # print("Found fund_tx: ", fundtx) + p2sh = P2SHBitcoinAddress(p2sh) + if fundtx['address'] == p2sh: + print("Found {0} in p2sh {1}, redeeming...".format(amount, p2sh)) -def find_transaction_to_address(p2sh): - bitcoind.importaddress(p2sh, "", False) - txs = bitcoind.listunspent() - for tx in txs: - if tx['address'] == CBitcoinAddress(p2sh): - print("Found tx to p2sh", p2sh) - return tx + 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]) -def new_bitcoin_addr(): - addr = bitcoind.getnewaddress() - return str(addr) + # 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: + 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} + else: + print("No contract for this p2sh found in database", p2sh) -def generate(num): - blocks = bitcoind.generate(num) - return blocks + def find_redeemblocknum(self, contract): + scriptarray = parse_script(contract.redeemScript) + redeemblocknum = scriptarray[8] + return int(redeemblocknum) + + def find_redeemAddr(self, contract): + scriptarray = parse_script(contract.redeemScript) + redeemer = scriptarray[6] + redeemAddr = P2PKHBitcoinAddress.from_bytes(x(redeemer)) + return redeemAddr + + def find_refundAddr(self, contract): + scriptarray = parse_script(contract.redeemScript) + funder = scriptarray[13] + refundAddr = P2PKHBitcoinAddress.from_bytes(x(funder)) + return refundAddr + + def find_transaction_to_address(self, p2sh): + self.bitcoind.importaddress(p2sh, "", False) + txs = self.bitcoind.listunspent() + for tx in txs: + if tx['address'] == CBitcoinAddress(p2sh): + print("Found tx to p2sh", p2sh) + return tx + + def new_bitcoin_addr(self): + addr = self.bitcoind.getnewaddress() + return str(addr) + + def generate(self, num): + blocks = self.bitcoind.generate(num) + return blocks diff --git a/xcat/db.py b/xcat/db.py index 9697b02..c976f45 100644 --- a/xcat/db.py +++ b/xcat/db.py @@ -6,8 +6,6 @@ import json import ast from xcat.trades import * -import xcat.bitcoinRPC as bitcoinRPC - db = plyvel.DB('/tmp/xcatDB', create_if_missing=True) preimageDB = plyvel.DB('/tmp/preimageDB', create_if_missing=True) diff --git a/xcat/protocol.py b/xcat/protocol.py index 5b819d5..165f9d5 100644 --- a/xcat/protocol.py +++ b/xcat/protocol.py @@ -2,12 +2,14 @@ import json import os, sys from pprint import pprint import xcat.zcashRPC as zcashRPC -import xcat.bitcoinRPC as bitcoinRPC from xcat.utils import * from xcat.trades import Contract, Trade import xcat.userInput as userInput import xcat.db as db from xcat.xcatconf import * +from xcat.bitcoinRPC import bitcoinProxy + +bitcoinRPC = bitcoinProxy() def find_secret_from_fundtx(currency, p2sh, fundtx): if currency == 'bitcoin': diff --git a/xcat/userInput.py b/xcat/userInput.py index 472ac63..6aa18b1 100644 --- a/xcat/userInput.py +++ b/xcat/userInput.py @@ -1,5 +1,6 @@ from xcat.utils import * from xcat.db import * +from xcat.bitcoinRPC import bitcoinProxy def enter_trade_id(): tradeid = input("Enter a unique identifier for this trade: ") @@ -34,6 +35,7 @@ def authorize_fund_sell(htlcTrade): response = input("Type 'enter' to allow this program to send funds on your behalf.") def get_initiator_addresses(): + bitcoinRPC = bitcoinProxy() btc_addr = input("Enter your bitcoin address or press enter to generate one: ") btc_addr = bitcoinRPC.new_bitcoin_addr() print(btc_addr) From 41e1c437ec671817a984153c45a8450c988c0397 Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Fri, 25 Aug 2017 15:22:38 -0700 Subject: [PATCH 2/3] Refactor zcashRPC --- xcat/bitcoinRPC.py | 15 +- xcat/cli.py | 17 +- xcat/protocol.py | 12 +- xcat/userInput.py | 2 + xcat/utils.py | 11 -- xcat/zcashRPC.py | 387 +++++++++++++++++++++++---------------------- 6 files changed, 229 insertions(+), 215 deletions(-) diff --git a/xcat/bitcoinRPC.py b/xcat/bitcoinRPC.py index 2735ba3..487e3a1 100644 --- a/xcat/bitcoinRPC.py +++ b/xcat/bitcoinRPC.py @@ -15,13 +15,11 @@ from bitcoin.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH from bitcoin.wallet import CBitcoinAddress, CBitcoinSecret, P2SHBitcoinAddress, P2PKHBitcoinAddress from xcat.utils import * -from xcat.zcashRPC import parse_script FEE = 0.001*COIN - class bitcoinProxy(): - def __init__(self, network='testnet', timeout=900): + def __init__(self, network='regtest', timeout=900): self.network = network self.timeout = timeout @@ -143,17 +141,17 @@ class bitcoinProxy(): def redeem_contract(self, contract, secret): print("Parsing script for redeem_contract...") - scriptarray = parse_script(contract.redeemScript) + scriptarray = self.parse_script(contract.redeemScript) redeemblocknum = scriptarray[8] redeemPubKey = P2PKHBitcoinAddress.from_bytes(x(scriptarray[6])) refundPubKey = P2PKHBitcoinAddress.from_bytes(x(scriptarray[13])) p2sh = contract.p2sh #checking there are funds in the address - amount = check_funds(p2sh) + amount = self.check_funds(p2sh) if(amount == 0): print("address ", p2sh, " not funded") quit() - fundtx = find_transaction_to_address(p2sh) + fundtx = self.find_transaction_to_address(p2sh) amount = fundtx['amount'] / COIN # print("Found fund_tx: ", fundtx) p2sh = P2SHBitcoinAddress(p2sh) @@ -195,6 +193,11 @@ class bitcoinProxy(): else: print("No contract for this p2sh found in database", p2sh) + def parse_script(self, script_hex): + redeemScript = self.bitcoind.call('decodescript', script_hex) + scriptarray = redeemScript['asm'].split(' ') + return scriptarray + def find_redeemblocknum(self, contract): scriptarray = parse_script(contract.redeemScript) redeemblocknum = scriptarray[8] diff --git a/xcat/cli.py b/xcat/cli.py index 585e7c1..9b412b4 100644 --- a/xcat/cli.py +++ b/xcat/cli.py @@ -24,7 +24,12 @@ def checkSellStatus(tradeid): secret = db.get_secret(tradeid) print("Retrieved secret to redeem funds for {0}: {1}".format(tradeid, secret)) txs = seller_redeem_p2sh(trade, secret) - trade.buy.redeem_tx = txs['redeem_tx'] + if 'redeem_tx' in txs: + trade.buy.redeem_tx = txs['redeem_tx'] + print("Redeem tx: ", txs['redeem_tx']) + elif 'refund_tx' in txs: + 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) @@ -87,8 +92,12 @@ def checkBuyStatus(tradeid): if secret != None: print("Found secret on blockchain in seller's redeem tx: ", secret) txs = redeem_p2sh(trade.sell, secret) - trade.sell.redeem_tx = txs['redeem_tx'] - print("Redeem txid: ", trade.sell.redeem_tx) + if 'redeem_tx' in txs: + trade.sell.redeem_tx = txs['redeem_tx'] + print("Redeem txid: ", trade.sell.redeem_tx) + elif 'refund_tx' in txs: + trade.sell.redeem_tx = txs['refund_tx'] + print("Refund tx: ", txs['refund_tx']) save_state(trade, tradeid) print("XCAT trade complete!") else: @@ -183,7 +192,7 @@ def main(): parser.add_argument("arguments", action="store", nargs="*", help="add arguments") 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 beta.") + 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() diff --git a/xcat/protocol.py b/xcat/protocol.py index 165f9d5..27f8721 100644 --- a/xcat/protocol.py +++ b/xcat/protocol.py @@ -1,15 +1,25 @@ import json import os, sys from pprint import pprint -import xcat.zcashRPC as zcashRPC from xcat.utils import * from xcat.trades import Contract, Trade import xcat.userInput as userInput import xcat.db as db from xcat.xcatconf import * from xcat.bitcoinRPC import bitcoinProxy +from xcat.zcashRPC import zcashProxy bitcoinRPC = bitcoinProxy() +zcashRPC = zcashProxy() + +def is_myaddr(address): + 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)) + return status def find_secret_from_fundtx(currency, p2sh, fundtx): if currency == 'bitcoin': diff --git a/xcat/userInput.py b/xcat/userInput.py index 6aa18b1..fdcb2cb 100644 --- a/xcat/userInput.py +++ b/xcat/userInput.py @@ -1,6 +1,7 @@ from xcat.utils import * from xcat.db import * from xcat.bitcoinRPC import bitcoinProxy +from xcat.zcashRPC import zcashProxy def enter_trade_id(): tradeid = input("Enter a unique identifier for this trade: ") @@ -36,6 +37,7 @@ def authorize_fund_sell(htlcTrade): def get_initiator_addresses(): bitcoinRPC = bitcoinProxy() + zcashRPC = zcashProxy() btc_addr = input("Enter your bitcoin address or press enter to generate one: ") btc_addr = bitcoinRPC.new_bitcoin_addr() print(btc_addr) diff --git a/xcat/utils.py b/xcat/utils.py index 9c290f4..85bf8bd 100644 --- a/xcat/utils.py +++ b/xcat/utils.py @@ -1,7 +1,5 @@ import hashlib, json, random, binascii import xcat.trades as trades -import xcat.bitcoinRPC as bitcoinRPC -import xcat.zcashRPC as zcashRPC import os ROOT_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) @@ -53,15 +51,6 @@ def find_role(contract): else: return 'fulfiller' -def is_myaddr(address): - 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)) - return status - ############################################ ########### Preimage utils ################# ############################################ diff --git a/xcat/zcashRPC.py b/xcat/zcashRPC.py index 8ffe56b..7c7ebbb 100644 --- a/xcat/zcashRPC.py +++ b/xcat/zcashRPC.py @@ -16,215 +16,216 @@ from zcash.core.script import CScript, OP_DUP, OP_IF, OP_ELSE, OP_ENDIF, OP_HASH from zcash.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH from zcash.wallet import CBitcoinAddress, CBitcoinSecret, P2SHBitcoinAddress, P2PKHBitcoinAddress -from xcat.utils import * +from xcat.utils import x2s -SelectParams('testnet') -# SelectParams('regtest') -zcashd = zcash.rpc.Proxy(timeout=900) FEE = 0.001*COIN -def x2s(hexstring): - """Convert hex to a utf-8 string""" - return binascii.unhexlify(hexstring).decode('utf-8') +class zcashProxy(): + def __init__(self, network='regtest', timeout=900): + self.network = network + self.timeout = timeout -def validateaddress(addr): - return zcashd.validateaddress(addr) + SelectParams(self.network) + self.zcashd = zcash.rpc.Proxy(timeout=self.timeout) -def get_keys(funder_address, redeemer_address): - fundpubkey = CBitcoinAddress(funder_address) - redeempubkey = CBitcoinAddress(redeemer_address) - return fundpubkey, redeempubkey + def validateaddress(self, addr): + return self.zcashd.validateaddress(addr) -def privkey(address): - zcashd.dumpprivkey(address) + def get_keys(self, funder_address, redeemer_address): + fundpubkey = CBitcoinAddress(funder_address) + redeempubkey = CBitcoinAddress(redeemer_address) + return fundpubkey, redeempubkey -def hashtimelockcontract(funder, redeemer, commitment, locktime): - funderAddr = CBitcoinAddress(funder) - redeemerAddr = CBitcoinAddress(redeemer) - if type(commitment) == str: - commitment = x(commitment) - # h = sha256(secret) - blocknum = zcashd.getblockcount() - print("Current blocknum on Zcash: ", blocknum) - redeemblocknum = blocknum + locktime - print("Redeemblocknum on Zcash: ", redeemblocknum) - # can rm op_dup and op_hash160 if you replace addrs with pubkeys (as raw hex/bin data?), and can rm last op_equalverify (for direct pubkey comparison) - zec_redeemScript = CScript([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]) - # print("Redeem script for p2sh contract on Zcash blockchain: ", b2x(zec_redeemScript)) - txin_scriptPubKey = zec_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) - print("p2sh computed: ", p2sh) - # Import address as soon as you create it - zcashd.importaddress(p2sh, "", False) - # Returning all this to be saved locally in p2sh.json - return {'p2sh': p2sh, 'redeemblocknum': redeemblocknum, 'redeemScript': b2x(zec_redeemScript), 'redeemer': redeemer, 'funder': funder, 'locktime': locktime} + def privkey(self, address): + self.zcashd.dumpprivkey(address) -def fund_htlc(p2sh, amount): - send_amount = float(amount)*COIN - # Import addr at same time as you fund - zcashd.importaddress(p2sh, "", False) - fund_txid = zcashd.sendtoaddress(p2sh, send_amount) - txid = b2x(lx(b2x(fund_txid))) - return txid + def hashtimelockcontract(self, funder, redeemer, commitment, locktime): + funderAddr = CBitcoinAddress(funder) + redeemerAddr = CBitcoinAddress(redeemer) + if type(commitment) == str: + commitment = x(commitment) + # h = sha256(secret) + blocknum = self.zcashd.getblockcount() + print("Current blocknum on Zcash: ", blocknum) + redeemblocknum = blocknum + locktime + print("Redeemblocknum on Zcash: ", redeemblocknum) + # can rm op_dup and op_hash160 if you replace addrs with pubkeys (as raw hex/bin data?), and can rm last op_equalverify (for direct pubkey comparison) + zec_redeemScript = CScript([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]) + # print("Redeem script for p2sh contract on Zcash blockchain: ", b2x(zec_redeemScript)) + txin_scriptPubKey = zec_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) + print("p2sh computed: ", p2sh) + # Import address as soon as you create it + self.zcashd.importaddress(p2sh, "", False) + # Returning all this to be saved locally in p2sh.json + return {'p2sh': p2sh, 'redeemblocknum': redeemblocknum, 'redeemScript': b2x(zec_redeemScript), 'redeemer': redeemer, 'funder': funder, 'locktime': locktime} -# Following two functions are about the same -def check_funds(p2sh): - zcashd.importaddress(p2sh, "", False) - # Get amount in address - amount = zcashd.getreceivedbyaddress(p2sh, 0) - amount = amount/COIN - return amount + def fund_htlc(self, p2sh, amount): + send_amount = float(amount)*COIN + # Import addr at same time as you fund + self.zcashd.importaddress(p2sh, "", False) + fund_txid = self.zcashd.sendtoaddress(p2sh, send_amount) + txid = b2x(lx(b2x(fund_txid))) + return txid -def get_fund_status(p2sh): - zcashd.importaddress(p2sh, "", False) - amount = zcashd.getreceivedbyaddress(p2sh, 0) - amount = amount/COIN - print("Amount in zcash p2sh: ", amount, p2sh) - if amount > 0: - return 'funded' - else: - return 'empty' + # Following two functions are about the same + def check_funds(self, p2sh): + self.zcashd.importaddress(p2sh, "", False) + # Get amount in address + amount = self.zcashd.getreceivedbyaddress(p2sh, 0) + amount = amount/COIN + return amount -def get_tx_details(txid): - fund_txinfo = zcashd.gettransaction(txid) - return fund_txinfo['details'][0] - -def find_transaction_to_address(p2sh): - zcashd.importaddress(p2sh, "", False) - txs = zcashd.listunspent(0, 100) - for tx in txs: - if tx['address'] == CBitcoinAddress(p2sh): - print("Found tx to p2sh", p2sh) - return tx - -def find_secret(p2sh, fundtx_input): - txs = zcashd.call('listtransactions', "*", 20, 0, True) - for tx in txs: - raw = zcashd.gettransaction(lx(tx['txid']))['hex'] - decoded = zcashd.decoderawtransaction(raw) - if('txid' in decoded['vin'][0]): - sendid = decoded['vin'][0]['txid'] - if (sendid == fundtx_input ): - print("Found funding tx: ", sendid) - return parse_secret(lx(tx['txid'])) - print("Redeem transaction with secret not found") - return - -def parse_secret(txid): - raw = zcashd.gettransaction(txid, True)['hex'] - decoded = zcashd.decoderawtransaction(raw) - scriptSig = decoded['vin'][0]['scriptSig'] - asm = scriptSig['asm'].split(" ") - pubkey = asm[1] - secret = x2s(asm[2]) - redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey)) - return secret - -def redeem_contract(contract, secret): - # How to find redeemScript and redeemblocknum from blockchain? - p2sh = contract.p2sh - #checking there are funds in the address - amount = check_funds(p2sh) - if(amount == 0): - print("Address ", p2sh, " not funded") - quit() - fundtx = find_transaction_to_address(p2sh) - amount = fundtx['amount'] / COIN - # print("Found fund_tx: ", fundtx) - 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 = zcashd.getblockcount() - print("\nCurrent blocknum at time of redeem on Zcash:", blockcount) - if blockcount < contract.redeemblocknum: - # TODO: parse the script once, up front. - redeemPubKey = 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 = 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 = zcashd.sendrawtransaction(tx) - redeem_tx = b2x(lx(b2x(txid))) - fund_tx = str(fundtx['outpoint']) - return {"redeem_tx": redeem_tx, "fund_tx": fund_tx} + def get_fund_status(self, p2sh): + self.zcashd.importaddress(p2sh, "", False) + amount = self.zcashd.getreceivedbyaddress(p2sh, 0) + amount = amount/COIN + print("Amount in zcash p2sh: ", amount, p2sh) + if amount > 0: + return 'funded' else: - print("nLocktime exceeded, refunding") - refundPubKey = find_refundAddr(contract) - print('refundPubKey', refundPubKey) - txid = 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: - print("No contract for this p2sh found in database", p2sh) + return 'empty' -def parse_script(script_hex): - redeemScript = zcashd.decodescript(script_hex) - scriptarray = redeemScript['asm'].split(' ') - return scriptarray + def get_tx_details(self, txid): + fund_txinfo = self.zcashd.gettransaction(txid) + return fund_txinfo['details'][0] -def find_redeemblocknum(contract): - print("In find_redeemblocknum") - scriptarray = parse_script(contract.redeemScript) - print("Returning scriptarray", scriptarray) - redeemblocknum = scriptarray[8] - return int(redeemblocknum) + def find_transaction_to_address(self, p2sh): + self.zcashd.importaddress(p2sh, "", False) + txs = self.zcashd.listunspent(0, 100) + for tx in txs: + if tx['address'] == CBitcoinAddress(p2sh): + print("Found tx to p2sh", p2sh) + return tx -def find_redeemAddr(contract): - scriptarray = parse_script(contract.redeemScript) - redeemer = scriptarray[6] - redeemAddr = P2PKHBitcoinAddress.from_bytes(x(redeemer)) - return redeemAddr + def find_secret(self, p2sh, fundtx_input): + txs = self.zcashd.call('listtransactions', "*", 20, 0, True) + for tx in txs: + raw = self.zcashd.gettransaction(lx(tx['txid']))['hex'] + decoded = self.zcashd.decoderawtransaction(raw) + 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") + return -def find_refundAddr(contract): - scriptarray = parse_script(contract.redeemScript) - funder = scriptarray[13] - refundAddr = P2PKHBitcoinAddress.from_bytes(x(funder)) - return refundAddr + def parse_secret(self, txid): + raw = self.zcashd.gettransaction(txid, True)['hex'] + decoded = self.zcashd.decoderawtransaction(raw) + scriptSig = decoded['vin'][0]['scriptSig'] + asm = scriptSig['asm'].split(" ") + pubkey = asm[1] + secret = x2s(asm[2]) + redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey)) + return secret -def find_recipient(contract): - # make this dependent on actual fund tx to p2sh, not contract - txid = contract.fund_tx - raw = zcashd.gettransaction(lx(txid), True)['hex'] - decoded = zcashd.decoderawtransaction(raw) - scriptSig = decoded['vin'][0]['scriptSig'] - print("Decoded", scriptSig) - asm = scriptSig['asm'].split(" ") - pubkey = asm[1] - initiator = CBitcoinAddress(contract.initiator) - fulfiller = CBitcoinAddress(contract.fulfiller) - print("Initiator", b2x(initiator)) - print("Fulfiller", b2x(fulfiller)) - print('pubkey', pubkey) - redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey)) - print('redeemPubkey', redeemPubkey) + def redeem_contract(self, contract, secret): + # How to find redeemScript and redeemblocknum from blockchain? + p2sh = contract.p2sh + #checking there are funds in the address + amount = self.check_funds(p2sh) + if(amount == 0): + print("Address ", p2sh, " not funded") + quit() + fundtx = self.find_transaction_to_address(p2sh) + amount = fundtx['amount'] / COIN + # print("Found fund_tx: ", fundtx) + p2sh = P2SHBitcoinAddress(p2sh) + if fundtx['address'] == p2sh: + print("Found {0} in p2sh {1}, redeeming...".format(amount, p2sh)) -def new_zcash_addr(): - addr = zcashd.getnewaddress() - return str(addr) + # 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) -def generate(num): - blocks = zcashd.generate(num) - return blocks + 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: + 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} + else: + print("No contract for this p2sh found in database", p2sh) + + def parse_script(self, script_hex): + redeemScript = self.zcashd.decodescript(script_hex) + scriptarray = redeemScript['asm'].split(' ') + return scriptarray + + def find_redeemblocknum(self, contract): + print("In find_redeemblocknum") + scriptarray = self.parse_script(contract.redeemScript) + print("Returning scriptarray", scriptarray) + redeemblocknum = scriptarray[8] + return int(redeemblocknum) + + def find_redeemAddr(self, contract): + scriptarray = self.parse_script(contract.redeemScript) + redeemer = scriptarray[6] + redeemAddr = P2PKHBitcoinAddress.from_bytes(x(redeemer)) + return redeemAddr + + def find_refundAddr(self, contract): + scriptarray = self.parse_script(contract.redeemScript) + funder = scriptarray[13] + refundAddr = P2PKHBitcoinAddress.from_bytes(x(funder)) + return refundAddr + + def find_recipient(self, contract): + # make this dependent on actual fund tx to p2sh, not contract + txid = contract.fund_tx + raw = self.zcashd.gettransaction(lx(txid), True)['hex'] + decoded = self.zcashd.decoderawtransaction(raw) + scriptSig = decoded['vin'][0]['scriptSig'] + print("Decoded", scriptSig) + asm = scriptSig['asm'].split(" ") + pubkey = asm[1] + initiator = CBitcoinAddress(contract.initiator) + fulfiller = CBitcoinAddress(contract.fulfiller) + print("Initiator", b2x(initiator)) + print("Fulfiller", b2x(fulfiller)) + print('pubkey', pubkey) + redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey)) + print('redeemPubkey', redeemPubkey) + + def new_zcash_addr(self): + addr = self.zcashd.getnewaddress() + return str(addr) + + def generate(self, num): + blocks = self.zcashd.generate(num) + return blocks From 6f20bdbad026e0a0aadd44084bb6651b1b8e3bcd Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Fri, 25 Aug 2017 15:42:30 -0700 Subject: [PATCH 3/3] Pull addr validation up into cli.py --- xcat/cli.py | 9 +++++++++ xcat/utils.py | 12 ------------ 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/xcat/cli.py b/xcat/cli.py index 9b412b4..5b41fc1 100644 --- a/xcat/cli.py +++ b/xcat/cli.py @@ -143,6 +143,15 @@ def findtrade(tradeid): print(trade.toJSON()) return trade +def find_role(contract): + # When regtest created both addrs on same machine, role is both. + if is_myaddr(contract.initiator) and is_myaddr(contract.fulfiller): + return 'test' + elif is_myaddr(contract.initiator): + return 'initiator' + else: + return 'fulfiller' + def checktrade(tradeid): print("In checktrade") trade = db.get(tradeid) diff --git a/xcat/utils.py b/xcat/utils.py index 85bf8bd..5a1315c 100644 --- a/xcat/utils.py +++ b/xcat/utils.py @@ -39,18 +39,6 @@ def jsonformat(trade): 'buy': trade.buyContract.__dict__ } -############################################ -#### Role detection utils #### -############################################ -def find_role(contract): - # Obviously when regtest created both addrs on same machine, role is both. - if is_myaddr(contract.initiator) and is_myaddr(contract.fulfiller): - return 'test' - elif is_myaddr(contract.initiator): - return 'initiator' - else: - return 'fulfiller' - ############################################ ########### Preimage utils ################# ############################################