Merge pull request #29 from frdwrd/better-tests

Refactoring and unittests
This commit is contained in:
arcalinea 2017-10-19 07:02:02 -07:00 committed by GitHub
commit 76c1729031
19 changed files with 1287 additions and 455 deletions

15
.gitignore vendored
View File

@ -1,4 +1,15 @@
*.pyc # xcat
xcat.egg-info/
.tmp/ .tmp/
# Python
*.pyc
*.egg-info/
# Virtual environment
venv/ venv/
# Unit test / coverage reports
.tox/
coverage/
.coverage
.cache

3
requirements-test.txt Normal file
View File

@ -0,0 +1,3 @@
flake8
pytest
pytest-cov

26
tox.ini Normal file
View File

@ -0,0 +1,26 @@
[tox]
envlist =
py3{5}
[pytest]
nonrecursedirs = .git .tox venv coverage
[testenv]
usedevelop = True
deps =
-rrequirements.txt
-rrequirements-test.txt
commands =
flake8 \
--ignore=E501,E266,W503 \
--exclude test.py \
xcat
pytest \
-q \
--junitxml=coverage/unit.xml \
--cov xcat \
--cov-report xml:coverage/coverage.xml \
--cov-report html:coverage/html/ \
--cov-report term-missing \
{posargs}

View File

@ -1,33 +1,40 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import sys import sys
import bitcoin
import bitcoin.rpc
from xcat.utils import x2s
from bitcoin.core import b2x, lx, x, COIN, CMutableTxOut
from bitcoin.core import CMutableTxIn, CMutableTransaction
from bitcoin.core.script import CScript, OP_DUP, OP_IF, OP_ELSE, OP_ENDIF
from bitcoin.core.script import OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG
from bitcoin.core.script import SignatureHash, SIGHASH_ALL, OP_FALSE, OP_DROP
from bitcoin.core.script import OP_CHECKLOCKTIMEVERIFY, OP_SHA256, OP_TRUE
from bitcoin.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH
from bitcoin.wallet import CBitcoinAddress, P2SHBitcoinAddress
from bitcoin.wallet import P2PKHBitcoinAddress
import logging
if sys.version_info.major < 3: if sys.version_info.major < 3:
sys.stderr.write('Sorry, Python 3.x required by this example.\n') sys.stderr.write('Sorry, Python 3.x required by this example.\n')
sys.exit(1) sys.exit(1)
import bitcoin FEE = 0.001 * COIN
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
class bitcoinProxy(): class bitcoinProxy():
def __init__(self, network='regtest', timeout=900): def __init__(self, network='regtest', timeout=900):
if network is not 'testnet' and network is not 'mainnet': if network not in ['testnet', 'mainnet', 'regtest']:
network='regtest' raise ValueError('Allowed networks are regtest, testnet, mainnet.')
if not isinstance(timeout, int) or timeout < 1:
raise ValueError('Timeout should be a positive integer.')
logging.debug("NETWORK in proxy: {0}".format(network)) logging.debug("NETWORK in proxy: {0}".format(network))
self.network = network self.network = network
self.timeout = timeout self.timeout = timeout
SelectParams(self.network) bitcoin.SelectParams(self.network)
self.bitcoind = bitcoin.rpc.Proxy(timeout=self.timeout) self.bitcoind = bitcoin.rpc.Proxy(timeout=self.timeout)
def validateaddress(self, addr): def validateaddress(self, addr):
@ -41,9 +48,9 @@ class bitcoinProxy():
print("TXINFO", decoded['vin'][0]) print("TXINFO", decoded['vin'][0])
if('txid' in decoded['vin'][0]): if('txid' in decoded['vin'][0]):
sendid = decoded['vin'][0]['txid'] sendid = decoded['vin'][0]['txid']
if (sendid == fundtx_input ): if (sendid == fundtx_input):
print("Found funding tx: ", sendid) print("Found funding tx: ", sendid)
return parse_secret(lx(tx['txid'])) return self.parse_secret(lx(tx['txid']))
print("Redeem transaction with secret not found") print("Redeem transaction with secret not found")
return return
@ -52,9 +59,9 @@ class bitcoinProxy():
decoded = self.bitcoind.call('decoderawtransaction', raw) decoded = self.bitcoind.call('decoderawtransaction', raw)
scriptSig = decoded['vin'][0]['scriptSig'] scriptSig = decoded['vin'][0]['scriptSig']
asm = scriptSig['asm'].split(" ") asm = scriptSig['asm'].split(" ")
pubkey = asm[1] # pubkey = asm[1]
secret = x2s(asm[2]) secret = x2s(asm[2])
redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey)) # redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey))
return secret return secret
def get_keys(self, funder_address, redeemer_address): def get_keys(self, funder_address, redeemer_address):
@ -77,18 +84,27 @@ class bitcoinProxy():
print("Current blocknum on Bitcoin: ", blocknum) print("Current blocknum on Bitcoin: ", blocknum)
redeemblocknum = blocknum + locktime redeemblocknum = blocknum + locktime
print("Redeemblocknum on Bitcoin: ", redeemblocknum) print("Redeemblocknum on Bitcoin: ", redeemblocknum)
redeemScript = CScript([OP_IF, OP_SHA256, commitment, OP_EQUALVERIFY,OP_DUP, OP_HASH160, redeemScript = CScript([
redeemerAddr, OP_ELSE, redeemblocknum, OP_CHECKLOCKTIMEVERIFY, OP_DROP, OP_DUP, OP_HASH160, OP_IF, OP_SHA256, commitment, OP_EQUALVERIFY, OP_DUP, OP_HASH160,
funderAddr, OP_ENDIF,OP_EQUALVERIFY, OP_CHECKSIG]) redeemerAddr, OP_ELSE, redeemblocknum, OP_CHECKLOCKTIMEVERIFY,
# print("Redeem script for p2sh contract on Bitcoin blockchain: {0}".format(b2x(redeemScript))) 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() txin_scriptPubKey = redeemScript.to_p2sh_scriptPubKey()
# Convert the P2SH scriptPubKey to a base58 Bitcoin address # Convert the P2SH scriptPubKey to a base58 Bitcoin address
txin_p2sh_address = CBitcoinAddress.from_scriptPubKey(txin_scriptPubKey) txin_p2sh_address = CBitcoinAddress.from_scriptPubKey(
txin_scriptPubKey)
p2sh = str(txin_p2sh_address) p2sh = str(txin_p2sh_address)
# Import address at same time you create # Import address at same time you create
self.bitcoind.importaddress(p2sh, "", False) self.bitcoind.importaddress(p2sh, "", False)
print("p2sh computed", p2sh) print("p2sh computed", p2sh)
return {'p2sh': p2sh, 'redeemblocknum': redeemblocknum, 'redeemScript': b2x(redeemScript), 'redeemer': redeemer, 'funder': funder, 'locktime': locktime} return {'p2sh': p2sh,
'redeemblocknum': redeemblocknum,
'redeemScript': b2x(redeemScript),
'redeemer': redeemer,
'funder': funder,
'locktime': locktime}
def fund_htlc(self, p2sh, amount): def fund_htlc(self, p2sh, amount):
send_amount = float(amount) * COIN send_amount = float(amount) * COIN
@ -103,20 +119,20 @@ class bitcoinProxy():
self.bitcoind.importaddress(p2sh, "", False) self.bitcoind.importaddress(p2sh, "", False)
# Get amount in address # Get amount in address
amount = self.bitcoind.getreceivedbyaddress(p2sh, 0) amount = self.bitcoind.getreceivedbyaddress(p2sh, 0)
amount = amount/COIN amount = amount / COIN
return amount return amount
def get_fund_status(self, p2sh): def get_fund_status(self, p2sh):
self.bitcoind.importaddress(p2sh, "", False) self.bitcoind.importaddress(p2sh, "", False)
amount = self.bitcoind.getreceivedbyaddress(p2sh, 0) amount = self.bitcoind.getreceivedbyaddress(p2sh, 0)
amount = amount/COIN amount = amount / COIN
print("Amount in bitcoin p2sh: ", amount, p2sh) print("Amount in bitcoin p2sh: ", amount, p2sh)
if amount > 0: if amount > 0:
return 'funded' return 'funded'
else: else:
return 'empty' return 'empty'
## TODO: FIX search for p2sh in block # TODO: FIX search for p2sh in block
def search_p2sh(self, block, p2sh): def search_p2sh(self, block, p2sh):
print("Fetching block...") print("Fetching block...")
blockdata = self.bitcoind.getblock(lx(block)) blockdata = self.bitcoind.getblock(lx(block))
@ -145,9 +161,9 @@ class bitcoinProxy():
scriptarray = self.parse_script(contract.redeemScript) scriptarray = self.parse_script(contract.redeemScript)
redeemblocknum = scriptarray[8] redeemblocknum = scriptarray[8]
self.redeemPubKey = P2PKHBitcoinAddress.from_bytes(x(scriptarray[6])) self.redeemPubKey = P2PKHBitcoinAddress.from_bytes(x(scriptarray[6]))
refundPubKey = P2PKHBitcoinAddress.from_bytes(x(scriptarray[13])) # refundPubKey = P2PKHBitcoinAddress.from_bytes(x(scriptarray[13]))
p2sh = contract.p2sh p2sh = contract.p2sh
#checking there are funds in the address # checking there are funds in the address
amount = self.check_funds(p2sh) amount = self.check_funds(p2sh)
if(amount == 0): if(amount == 0):
print("address ", p2sh, " not funded") print("address ", p2sh, " not funded")
@ -174,7 +190,9 @@ class bitcoinProxy():
# TODO: Compare with script on blockchain? # TODO: Compare with script on blockchain?
redeemScript = CScript(x(contract.redeemScript)) redeemScript = CScript(x(contract.redeemScript))
txin = CMutableTxIn(fundtx['outpoint']) txin = CMutableTxIn(fundtx['outpoint'])
txout = CMutableTxOut(fundtx['amount'] - FEE, self.redeemPubKey.to_scriptPubKey()) txout = CMutableTxOut(fundtx['amount'] - FEE,
self.redeemPubKey.to_scriptPubKey())
# Create the unsigned raw transaction. # Create the unsigned raw transaction.
tx = CMutableTransaction([txin], [txout]) tx = CMutableTransaction([txin], [txout])
sighash = SignatureHash(redeemScript, tx, 0, SIGHASH_ALL) sighash = SignatureHash(redeemScript, tx, 0, SIGHASH_ALL)
@ -182,17 +200,19 @@ class bitcoinProxy():
privkey = self.bitcoind.dumpprivkey(self.redeemPubKey) privkey = self.bitcoind.dumpprivkey(self.redeemPubKey)
sig = privkey.sign(sighash) + bytes([SIGHASH_ALL]) sig = privkey.sign(sighash) + bytes([SIGHASH_ALL])
preimage = secret.encode('utf-8') preimage = secret.encode('utf-8')
txin.scriptSig = CScript([sig, privkey.pub, preimage, OP_TRUE, redeemScript]) txin.scriptSig = CScript([sig, privkey.pub, preimage,
OP_TRUE, redeemScript])
# print("txin.scriptSig", b2x(txin.scriptSig)) # print("txin.scriptSig", b2x(txin.scriptSig))
txin_scriptPubKey = redeemScript.to_p2sh_scriptPubKey() txin_scriptPubKey = redeemScript.to_p2sh_scriptPubKey()
print('Raw redeem transaction hex: ', b2x(tx.serialize())) print('Raw redeem transaction hex: ', b2x(tx.serialize()))
VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,)) VerifyScript(txin.scriptSig, txin_scriptPubKey,
tx, 0, (SCRIPT_VERIFY_P2SH,))
print("Script verified, sending raw transaction...") print("Script verified, sending raw transaction...")
txid = self.bitcoind.sendrawtransaction(tx) txid = self.bitcoind.sendrawtransaction(tx)
fund_tx = str(fundtx['outpoint']) fund_tx = str(fundtx['outpoint'])
redeem_tx = b2x(lx(b2x(txid))) redeem_tx = b2x(lx(b2x(txid)))
return {"redeem_tx": redeem_tx, "fund_tx": fund_tx} return {"redeem_tx": redeem_tx, "fund_tx": fund_tx}
def refund(self, contract): def refund(self, contract):
fundtx = self.find_transaction_to_address(contract.p2sh) fundtx = self.find_transaction_to_address(contract.p2sh)
@ -202,7 +222,9 @@ class bitcoinProxy():
redeemScript = CScript(x(contract.redeemScript)) redeemScript = CScript(x(contract.redeemScript))
txin = CMutableTxIn(fundtx['outpoint']) txin = CMutableTxIn(fundtx['outpoint'])
txout = CMutableTxOut(fundtx['amount'] - FEE, refundPubKey.to_scriptPubKey()) txout = CMutableTxOut(fundtx['amount'] - FEE,
refundPubKey.to_scriptPubKey())
# Create the unsigned raw transaction. # Create the unsigned raw transaction.
tx = CMutableTransaction([txin], [txout]) tx = CMutableTransaction([txin], [txout])
# Set nSequence and nLockTime # Set nSequence and nLockTime
@ -211,15 +233,18 @@ class bitcoinProxy():
sighash = SignatureHash(redeemScript, tx, 0, SIGHASH_ALL) sighash = SignatureHash(redeemScript, tx, 0, SIGHASH_ALL)
privkey = self.bitcoind.dumpprivkey(refundPubKey) privkey = self.bitcoind.dumpprivkey(refundPubKey)
sig = privkey.sign(sighash) + bytes([SIGHASH_ALL]) sig = privkey.sign(sighash) + bytes([SIGHASH_ALL])
# Sign without secret # Sign without secret
txin.scriptSig = CScript([sig, privkey.pub, OP_FALSE, redeemScript]) txin.scriptSig = CScript([sig, privkey.pub, OP_FALSE, redeemScript])
# txin.nSequence = 2185 # txin.nSequence = 2185
txin_scriptPubKey = redeemScript.to_p2sh_scriptPubKey() txin_scriptPubKey = redeemScript.to_p2sh_scriptPubKey()
print('Raw redeem transaction hex: {0}'.format(b2x(tx.serialize()))) print('Raw redeem transaction hex: {0}'.format(b2x(tx.serialize())))
res = VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,)) res = VerifyScript(txin.scriptSig, txin_scriptPubKey,
tx, 0, (SCRIPT_VERIFY_P2SH,))
print("Script verified, sending raw transaction... (NOT)", res) print("Script verified, sending raw transaction... (NOT)", res)
txid = self.bitcoind.sendrawtransaction(tx) txid = self.bitcoind.sendrawtransaction(tx)
refund_tx = b2x(lx(b2x(txid))) refund_tx = b2x(lx(b2x(txid)))
fund_tx = str(fundtx['outpoint']) fund_tx = str(fundtx['outpoint'])
return {"refund_tx": refund_tx, "fund_tx": fund_tx} return {"refund_tx": refund_tx, "fund_tx": fund_tx}
@ -229,12 +254,12 @@ class bitcoinProxy():
return scriptarray return scriptarray
def find_redeemblocknum(self, contract): def find_redeemblocknum(self, contract):
scriptarray = parse_script(contract.redeemScript) scriptarray = self.parse_script(contract.redeemScript)
redeemblocknum = scriptarray[8] redeemblocknum = scriptarray[8]
return int(redeemblocknum) return int(redeemblocknum)
def find_redeemAddr(self, contract): def find_redeemAddr(self, contract):
scriptarray = parse_script(contract.redeemScript) scriptarray = self.parse_script(contract.redeemScript)
redeemer = scriptarray[6] redeemer = scriptarray[6]
redeemAddr = P2PKHBitcoinAddress.from_bytes(x(redeemer)) redeemAddr = P2PKHBitcoinAddress.from_bytes(x(redeemer))
return redeemAddr return redeemAddr

View File

@ -1,98 +1,132 @@
import argparse, textwrap import argparse
from xcat.utils import * import textwrap
import xcat.db as db
import xcat.userInput as userInput
from xcat.trades import *
from xcat.protocol import *
import subprocess import subprocess
import os
import logging
from xcat.db import DB
import xcat.userInput as userInput
import xcat.utils as utils
from xcat.protocol import Protocol
from xcat.trades import Trade
def save_state(trade, tradeid): def save_state(trade, tradeid):
save(trade) db = DB()
utils.save(trade)
db.create(trade, tradeid) db.create(trade, tradeid)
def checkSellStatus(tradeid): def checkSellStatus(tradeid):
db = DB()
protocol = Protocol()
trade = db.get(tradeid) trade = db.get(tradeid)
status = seller_check_status(trade) status = seller_check_status(trade)
print("Trade status: {0}\n".format(status)) print("Trade status: {0}\n".format(status))
if status == 'init': if status == 'init':
userInput.authorize_fund_sell(trade) userInput.authorize_fund_sell(trade)
fund_tx = fund_sell_contract(trade) fund_tx = protocol.fund_sell_contract(trade)
print("Sent fund_tx", fund_tx) print("Sent fund_tx", fund_tx)
trade.sell.fund_tx = fund_tx trade.sell.fund_tx = fund_tx
save_state(trade, tradeid) save_state(trade, tradeid)
elif status == 'buyerFunded': elif status == 'buyerFunded':
secret = db.get_secret(tradeid) secret = db.get_secret(tradeid)
print("Retrieved secret to redeem funds for {0}: {1}".format(tradeid, secret)) print("Retrieved secret to redeem funds for "
txs = seller_redeem_p2sh(trade, secret) "{0}: {1}".format(tradeid, secret))
txs = protocol.seller_redeem_p2sh(trade, secret)
if 'redeem_tx' in txs: if 'redeem_tx' in txs:
trade.buy.redeem_tx = txs['redeem_tx'] trade.buy.redeem_tx = txs['redeem_tx']
print("Redeem tx: ", txs['redeem_tx']) print("Redeem tx: ", txs['redeem_tx'])
if 'refund_tx' in txs: if 'refund_tx' in txs:
trade.buy.redeem_tx = txs['refund_tx'] trade.buy.redeem_tx = txs['refund_tx']
print("Buyer refund tx: ", txs['refund_tx']) print("Buyer refund tx: ", txs['refund_tx'])
txs = refund_contract(trade.sell) # Refund to seller txs = protocol.refund_contract(trade.sell) # Refund to seller
print("Your refund txid: ", txs['refund_tx']) print("Your refund txid: ", txs['refund_tx'])
save_state(trade, tradeid) save_state(trade, tradeid)
cleanup(tradeid) # Remove from db? Or just from temporary file storage
utils.cleanup(tradeid)
elif status == 'sellerFunded': elif status == 'sellerFunded':
print("Buyer has not yet funded the contract where you offered to buy {0}, please wait for them to complete their part.".format(trade.buy.currency)) print("Buyer has not yet funded the contract where you offered to "
"buy {0}, please wait for them to complete "
"their part.".format(trade.buy.currency))
elif status == 'sellerRedeemed': elif status == 'sellerRedeemed':
print("You have already redeemed the p2sh on the second chain of this trade.") print("You have already redeemed the p2sh on the second chain of "
"this trade.")
def buyer_check_status(trade): def buyer_check_status(trade):
sellState = check_fund_status(trade.sell.currency, trade.sell.p2sh) protocol = Protocol()
buyState = check_fund_status(trade.buy.currency, trade.buy.p2sh) sellState = protocol.check_fund_status(
trade.sell.currency, trade.sell.p2sh)
buyState = protocol.check_fund_status(
trade.buy.currency, trade.buy.p2sh)
if sellState == 'funded' and buyState == 'empty': if sellState == 'funded' and buyState == 'empty':
return 'sellerFunded' # step1 return 'sellerFunded' # step1
# TODO: Find funding txid. How does buyer get seller redeemed tx? # TODO: Find funding txid. How does buyer get seller redeemed tx?
elif sellState == 'funded' and hasattr(trade.buy, 'fund_tx'): elif sellState == 'funded' and hasattr(trade.buy, 'fund_tx'):
return 'sellerRedeemed' # step3 return 'sellerRedeemed' # step3
elif sellState == 'funded' and buyState == 'funded': elif sellState == 'funded' and buyState == 'funded':
return 'buyerFunded' # step2 return 'buyerFunded' # step2
elif sellState == 'empty' and buyState == 'empty': elif sellState == 'empty' and buyState == 'empty':
if hasattr(trade.sell, 'redeem_tx'): if hasattr(trade.sell, 'redeem_tx'):
return 'buyerRedeemed' # step4 return 'buyerRedeemed' # step4
else: else:
return 'init' return 'init'
def seller_check_status(trade): def seller_check_status(trade):
sellState = check_fund_status(trade.sell.currency, trade.sell.p2sh) protocol = Protocol()
buyState = check_fund_status(trade.buy.currency, trade.buy.p2sh) sellState = protocol.check_fund_status(
trade.sell.currency, trade.sell.p2sh)
buyState = protocol.check_fund_status(
trade.buy.currency, trade.buy.p2sh)
if sellState == 'funded' and buyState == 'empty': if sellState == 'funded' and buyState == 'empty':
return 'sellerFunded' # step1 return 'sellerFunded' # step1
elif sellState == 'funded' and hasattr(trade.buy, 'redeem_tx'): elif sellState == 'funded' and hasattr(trade.buy, 'redeem_tx'):
return 'sellerRedeemed' # step3 return 'sellerRedeemed' # step3
# TODO: How does seller get buyer funded tx? # TODO: How does seller get buyer funded tx?
elif sellState == 'funded' and buyState == 'funded': elif sellState == 'funded' and buyState == 'funded':
return 'buyerFunded' # step2 return 'buyerFunded' # step2
elif sellState == 'empty' and buyState == 'empty': elif sellState == 'empty' and buyState == 'empty':
if hasattr(trade.buy, 'redeem_tx'): if hasattr(trade.buy, 'redeem_tx'):
return 'buyerRedeemed' # step4 return 'buyerRedeemed' # step4
else: else:
return 'init' # step0 return 'init' # step0
def checkBuyStatus(tradeid): def checkBuyStatus(tradeid):
db = DB()
protocol = Protocol()
trade = db.get(tradeid) trade = db.get(tradeid)
status = buyer_check_status(trade) status = buyer_check_status(trade)
print("Trade status: {0}\n".format(status)) print("Trade status: {0}\n".format(status))
if status == 'init': if status == 'init':
print("Trade has not yet started, waiting for seller to fund the sell p2sh.") print("Trade has not yet started, waiting for seller to fund the "
"sell p2sh.")
elif status == 'buyerRedeemed': elif status == 'buyerRedeemed':
print("This trade is complete, both sides redeemed.") print("This trade is complete, both sides redeemed.")
elif status == 'sellerFunded': elif status == 'sellerFunded':
print("One active trade available, fulfilling buyer contract...") print("One active trade available, fulfilling buyer contract...")
input("Type 'enter' to allow this program to send funds on your behalf.") input("Type 'enter' to allow this program to send funds on your "
"behalf.")
print("Trade commitment", trade.commitment) print("Trade commitment", trade.commitment)
# if verify_p2sh(trade): # if verify_p2sh(trade):
fund_tx = fund_contract(trade.buy) fund_tx = protocol.fund_contract(trade.buy)
print("\nYou sent this funding tx: ", fund_tx) print("\nYou sent this funding tx: ", fund_tx)
trade.buy.fund_tx = fund_tx trade.buy.fund_tx = fund_tx
save_state(trade, tradeid) save_state(trade, tradeid)
elif status == 'sellerRedeemed': elif status == 'sellerRedeemed':
secret = find_secret_from_fundtx(trade.buy.currency, trade.buy.p2sh, trade.buy.fund_tx) secret = protocol.find_secret_from_fundtx(trade.buy.currency,
if secret != None: trade.buy.p2sh,
trade.buy.fund_tx)
if secret is not None:
print("Found secret on blockchain in seller's redeem tx: ", secret) print("Found secret on blockchain in seller's redeem tx: ", secret)
txs = redeem_p2sh(trade.sell, secret) txs = protocol.redeem_p2sh(trade.sell, secret)
if 'redeem_tx' in txs: if 'redeem_tx' in txs:
trade.sell.redeem_tx = txs['redeem_tx'] trade.sell.redeem_tx = txs['redeem_tx']
print("Redeem txid: ", trade.sell.redeem_tx) print("Redeem txid: ", trade.sell.redeem_tx)
@ -105,18 +139,22 @@ def checkBuyStatus(tradeid):
# Search if tx has been refunded from p2sh # Search if tx has been refunded from p2sh
print("Secret not found in redeemtx") print("Secret not found in redeemtx")
# Import a trade in hex, and save to db # Import a trade in hex, and save to db
def importtrade(tradeid, hexstr=''): def importtrade(tradeid, hexstr=''):
trade = x2s(hexstr) protocol = Protocol()
trade = db.instantiate(trade) trade = utils.x2s(hexstr)
import_addrs(trade) trade = Trade(trade)
protocol.import_addrs(trade)
print(trade.toJSON()) print(trade.toJSON())
save_state(trade, tradeid) save_state(trade, tradeid)
def wormhole_importtrade(): def wormhole_importtrade():
res = subprocess.call('wormhole receive', shell=True) res = subprocess.call('wormhole receive', shell=True)
if res == 0: if res == 0:
tradeid = input("Enter filename of received trade data to import (printed on line above): ") tradeid = input("Enter filename of received trade data to import "
"(printed on line above): ")
with open(tradeid) as infile: with open(tradeid) as infile:
hexstr = infile.readline().strip() hexstr = infile.readline().strip()
importtrade(tradeid, hexstr) importtrade(tradeid, hexstr)
@ -125,12 +163,14 @@ def wormhole_importtrade():
else: else:
print("Importing trade using magic-wormhole failed.") print("Importing trade using magic-wormhole failed.")
# Export a trade by its tradeid # Export a trade by its tradeid
def exporttrade(tradeid, wormhole=False): def exporttrade(tradeid, wormhole=False):
trade = db.get(tradeid) db = DB()
hexstr = s2x(trade.toJSON()) trade = db.get(tradeid)
hexstr = utils.s2x(trade.toJSON())
if wormhole: if wormhole:
tradefile = os.path.join(ROOT_DIR, '.tmp/{0}'.format(tradeid)) tradefile = os.path.join(utils.ROOT_DIR, '.tmp/{0}'.format(tradeid))
print(tradefile) print(tradefile)
with open(tradefile, '+w') as outfile: with open(tradefile, '+w') as outfile:
outfile.write(hexstr) outfile.write(hexstr)
@ -140,56 +180,83 @@ def exporttrade(tradeid, wormhole=False):
print(hexstr) print(hexstr)
return hexstr return hexstr
def findtrade(tradeid): def findtrade(tradeid):
db = DB()
trade = db.get(tradeid) trade = db.get(tradeid)
print(trade.toJSON()) print(trade.toJSON())
return trade return trade
def find_role(contract): def find_role(contract):
protocol = Protocol()
# When regtest created both addrs on same machine, role is both. # When regtest created both addrs on same machine, role is both.
if is_myaddr(contract.initiator) and is_myaddr(contract.fulfiller): if protocol.is_myaddr(contract.initiator):
return 'test' if protocol.is_myaddr(contract.fulfiller):
elif is_myaddr(contract.initiator): return 'test'
return 'initiator' else:
return 'initiator'
else: else:
return 'fulfiller' if protocol.is_myaddr(contract.fulfiller):
return 'fulfiller'
else:
raise ValueError('You are not a participant in this contract.')
def checktrade(tradeid): def checktrade(tradeid):
db = DB()
print("In checktrade") print("In checktrade")
trade = db.get(tradeid) trade = db.get(tradeid)
if find_role(trade.sell) == 'test': if find_role(trade.sell) == 'test':
input("Is this a test? Both buyer and seller addresses are yours, press 'enter' to test.") input("Is this a test? Both buyer and seller addresses are yours, "
"press 'enter' to test.")
checkSellStatus(tradeid) checkSellStatus(tradeid)
checkBuyStatus(tradeid) checkBuyStatus(tradeid)
checkSellStatus(tradeid) checkSellStatus(tradeid)
checkBuyStatus(tradeid) checkBuyStatus(tradeid)
elif find_role(trade.sell) == 'initiator': elif find_role(trade.sell) == 'initiator':
print("You are the seller in this trade.") print("You are the seller in this trade.")
role = 'seller' # role = 'seller'
checkSellStatus(tradeid) checkSellStatus(tradeid)
else: else:
print("You are the buyer in this trade.") print("You are the buyer in this trade.")
role = 'buyer' # role = 'buyer'
checkBuyStatus(tradeid) checkBuyStatus(tradeid)
def newtrade(tradeid, **kwargs): def newtrade(tradeid, **kwargs):
protocol = Protocol()
print("Creating new XCAT trade...") print("Creating new XCAT trade...")
erase_trade() utils.erase_trade()
tradeid, trade= initialize_trade(tradeid, conf=kwargs['conf'], network=kwargs['network'])
conf = kwargs['conf'] if 'conf' in kwargs else 'regtest'
network = kwargs['network'] if 'network' in kwargs else 'regtest'
tradeid, trade = protocol.initialize_trade(
tradeid,
conf=conf,
network=network)
print("New trade created: {0}".format(trade)) 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") trade = protocol.seller_init(tradeid, trade, network=network)
print("\nUse 'xcat exporttrade [tradeid]' to export the trade and sent "
"to the buyer.\n")
save_state(trade, tradeid) save_state(trade, tradeid)
return trade return trade
def listtrades(): def listtrades():
db = DB()
print("Trades") print("Trades")
trades = db.dump() trade_list = db.dump()
for trade in trades: for trade in trade_list:
print("{0}: {1}".format(trade[0], trade[1])) print("{0}: {1}".format(trade[0], trade[1]))
def main(): def main():
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, parser = argparse.ArgumentParser(
formatter_class=argparse.RawTextHelpFormatter,
description=textwrap.dedent('''\ description=textwrap.dedent('''\
== Trades == == Trades ==
newtrade "tradeid" - create a new trade newtrade "tradeid" - create a new trade
@ -199,72 +266,105 @@ def main():
findtrade "tradeid" - find a trade by the tradeid findtrade "tradeid" - find a trade by the tradeid
''')) '''))
parser.add_argument("command", action="store", help="list commands")
parser.add_argument("arguments", action="store", nargs="*", help="add arguments") parser.add_argument(
parser.add_argument("-d", "--debug", action="store_true", help="Enable debug mode. Defaults to false") "command", action="store", help="list commands")
parser.add_argument("-w", "--wormhole", action="store_true", help="Transfer trade data through magic-wormhole") parser.add_argument(
parser.add_argument("-c", "--conf", action="store", help="Use default trade data in conf file.") "arguments", action="store", nargs="*", help="add arguments")
parser.add_argument("-n", "--network", action="store", help="Set network to regtest or mainnet. Defaults to testnet while in alpha.") parser.add_argument(
# parser.add_argument("--daemon", "-d", action="store_true", help="Run as daemon process") "-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() args = parser.parse_args()
if args.debug: if hasattr(args, 'debug'):
numeric_level = getattr(logging, 'DEBUG', None) numeric_level = getattr(logging, 'DEBUG', None)
logging.basicConfig(format='%(levelname)s: %(message)s', level=numeric_level) logging.basicConfig(format='%(levelname)s: %(message)s',
level=numeric_level)
else: else:
logging.basicConfig(format='%(levelname)s: %(message)s', level='INFO') logging.basicConfig(format='%(levelname)s: %(message)s',
level='INFO')
if args.network: if hasattr(args, 'network'):
NETWORK = args.network NETWORK = args.network
else: else:
NETWORK = 'testnet' NETWORK = 'testnet'
command = args.command command = args.command
if command == 'importtrade': if command == 'importtrade':
if args.wormhole: if args.wormhole:
wormhole_importtrade() wormhole_importtrade()
else: else:
if len(args.arguments) != 2: throw("Usage: importtrade [tradeid] [hexstring]") if len(args.arguments) != 2:
utils.throw("Usage: importtrade [tradeid] [hexstring]")
tradeid = args.arguments[0] tradeid = args.arguments[0]
hexstr = args.arguments[1] hexstr = args.arguments[1]
importtrade(tradeid, hexstr) importtrade(tradeid, hexstr)
elif command == 'exporttrade': elif command == 'exporttrade':
if len(args.arguments) < 1: throw("Usage: exporttrade [tradeid]") if len(args.arguments) < 1:
utils.throw("Usage: exporttrade [tradeid]")
tradeid = args.arguments[0] tradeid = args.arguments[0]
exporttrade(tradeid, args.wormhole) exporttrade(tradeid, args.wormhole)
elif command == "findtrade": elif command == "findtrade":
if len(args.arguments) < 1: throw("Usage: findtrade [tradeid]") if len(args.arguments) < 1:
utils.throw("Usage: findtrade [tradeid]")
print("Finding trade") print("Finding trade")
key = args.arguments[0] key = args.arguments[0]
findtrade(key) findtrade(key)
elif command == 'checktrade': elif command == 'checktrade':
if len(args.arguments) < 1: throw("Usage: checktrade [tradeid]") if len(args.arguments) < 1:
utils.throw("Usage: checktrade [tradeid]")
tradeid = args.arguments[0] tradeid = args.arguments[0]
checktrade(tradeid) checktrade(tradeid)
elif command == 'listtrades': elif command == 'listtrades':
listtrades() listtrades()
# TODO: function to tell if tradeid already exists for newtrade # TODO: function to tell if tradeid already exists for newtrade
elif command == 'newtrade': elif command == 'newtrade':
if len(args.arguments) < 1: throw("Usage: newtrade [tradeid]") if len(args.arguments) < 1:
utils.throw("Usage: newtrade [tradeid]")
tradeid = args.arguments[0] tradeid = args.arguments[0]
if args.conf == None: if args.conf is None:
newtrade(tradeid, network=NETWORK, conf='cli') newtrade(tradeid, network=NETWORK, conf='cli')
else: else:
newtrade(tradeid, network=NETWORK, conf=args.conf) newtrade(tradeid, network=NETWORK, conf=args.conf)
elif command == "daemon": elif command == "daemon":
#TODO: not implemented # TODO: not implemented
print("Run as daemon process") print("Run as daemon process")
# Ad hoc testing of workflow starts here # Ad hoc testing of workflow starts here
elif command == "step1": elif command == "step1":
tradeid = args.arguments[0] tradeid = args.arguments[0]
checkSellStatus(tradeid) checkSellStatus(tradeid)
elif command == "step2": elif command == "step2":
tradeid = args.arguments[0] tradeid = args.arguments[0]
checkBuyStatus(tradeid) checkBuyStatus(tradeid)
elif command == "step3": elif command == "step3":
# generate(31) # protocol = Protocol()
# protocol.generate(31)
tradeid = args.arguments[0] tradeid = args.arguments[0]
checkSellStatus(tradeid) checkSellStatus(tradeid)
elif command == "step4": elif command == "step4":
# generate(1) # generate(1)
tradeid = args.arguments[0] tradeid = args.arguments[0]

View File

@ -1,77 +1,77 @@
import plyvel import plyvel
from xcat.utils import *
import binascii
import sys
import json import json
import ast import xcat.utils as utils
from xcat.trades import * from xcat.trades import Trade
db = plyvel.DB('/tmp/xcatDB', create_if_missing=True)
preimageDB = plyvel.DB('/tmp/preimageDB', create_if_missing=True)
############################################# class DB():
######## Trades stored by tradeid ###########
#############################################
# Takes dict or obj, saves json str as bytes def __init__(self):
def create(trade, tradeid): self.db = plyvel.DB('/tmp/xcatDB', create_if_missing=True)
if type(trade) == dict: self.preimageDB = plyvel.DB('/tmp/preimageDB', create_if_missing=True)
trade = json.dumps(trade)
else:
trade = trade.toJSON()
db.put(b(tradeid), b(trade))
# Uses the funding txid as the key to save trade #############################################
def createByFundtx(trade): ######## Trades stored by tradeid ###########
trade = trade.toJSON() #############################################
# # Save trade by initiating txid
jt = json.loads(trade)
txid = jt['sell']['fund_tx']
db.put(b(txid), b(trade))
def get(tradeid): # Takes dict or obj, saves json str as bytes
rawtrade = db.get(b(tradeid)) def create(self, trade, tradeid):
tradestr = str(rawtrade, 'utf-8') if isinstance(trade, dict):
trade = instantiate(tradestr) trade = json.dumps(trade, sort_keys=True, indent=4)
return trade elif isinstance(trade, Trade):
trade = trade.toJSON()
else:
raise ValueError('Expected dictionary or Trade object')
self.db.put(utils.b(tradeid), utils.b(trade))
def instantiate(trade): # Uses the funding txid as the key to save trade
if type(trade) == str: def createByFundtx(self, trade):
tradestr = json.loads(trade) if isinstance(trade, dict):
trade = Trade(buy=Contract(tradestr['buy']), sell=Contract(tradestr['sell']), commitment=tradestr['commitment']) txid = trade['sell']['fund_tx']
trade = json.dumps(trade, sort_keys=True, indent=4)
elif isinstance(trade, Trade):
txid = trade.sell.fund_tx
trade = trade.toJSON()
else:
raise ValueError('Expected dictionary or Trade object')
self.db.put(utils.b(txid), utils.b(trade))
def get(self, tradeid):
rawtrade = self.db.get(utils.b(tradeid))
tradestr = str(rawtrade, 'utf-8')
trade = Trade(fromJSON=tradestr)
return trade return trade
############################################# #############################################
###### Preimages stored by tradeid ########## ###### Preimages stored by tradeid ##########
############################################# #############################################
# Stores secret locally in key/value store by tradeid # Stores secret locally in key/value store by tradeid
def save_secret(tradeid, secret): def save_secret(self, tradeid, secret):
res = preimageDB.put(b(tradeid), b(secret)) self.preimageDB.put(utils.b(tradeid), utils.b(secret))
def get_secret(tradeid): def get_secret(self, tradeid):
secret = preimageDB.get(b(tradeid)) secret = self.preimageDB.get(utils.b(tradeid))
secret = str(secret, 'utf-8') secret = str(secret, 'utf-8')
return secret return secret
#############################################
########## Dump or view db entries ##########
#############################################
############################################# def dump(self):
########## Dump or view db entries ########## results = []
############################################# with self.db.iterator() as it:
for k, v in it:
j = json.loads(str(v, 'utf-8'))
results.append((str(k, 'utf-8'), j))
return results
def dump(): def print_entries(self):
results = [] it = self.db.iterator()
with db.iterator() as it: with self.db.iterator() as it:
for k, v in it: for k, v in it:
j = json.loads(x2s(b2x(v))) j = json.loads(utils.x2s(utils.b2x(v)))
results.append((str(k, 'utf-8'), j)) print("Key:", k)
return results print('val: ', j)
# print('sell: ', j['sell'])
def print_entries():
it = db.iterator()
with db.iterator() as it:
for k, v in it:
j = json.loads(x2s(b2x(v)))
print("Key:", k)
print('val: ', j)
# print('sell: ', j['sell'])

View File

@ -1,219 +1,237 @@
import json import logging
import os, sys
from pprint import pprint
from xcat.utils import *
from xcat.trades import Contract, Trade
import xcat.userInput as userInput import xcat.userInput as userInput
import xcat.db as db import xcat.utils as utils
from xcat.xcatconf import * from xcat.xcatconf import ADDRS
from xcat.trades import Contract, Trade
from xcat.bitcoinRPC import bitcoinProxy from xcat.bitcoinRPC import bitcoinProxy
from xcat.zcashRPC import zcashProxy from xcat.zcashRPC import zcashProxy
import logging from xcat.db import DB
bitcoinRPC = bitcoinProxy()
zcashRPC = zcashProxy()
def generate(num): class Protocol():
bitcoinRPC.generate(num)
zcashRPC.generate(num)
def is_myaddr(address): def __init__(self):
# Handle different network prefixes self.bitcoinRPC = bitcoinProxy()
if address[:1] == 'm': self.zcashRPC = zcashProxy()
status = bitcoinRPC.validateaddress(address)
else:
status = zcashRPC.validateaddress(address)
logging.debug("Address status: ", status)
if status['isvalid'] is False:
raise ValueError("Invalid address: %s" % address)
elif 'ismine' in status:
status = status['ismine']
return status
def find_secret_from_fundtx(currency, p2sh, fundtx): def generate(self, num):
if currency == 'bitcoin': self.bitcoinRPC.generate(num)
secret = bitcoinRPC.find_secret(p2sh, fundtx) self.zcashRPC.generate(num)
elif currency == 'zcash':
secret = zcashRPC.find_secret(p2sh, fundtx)
else:
raise ValueError("Currency not recognized: ", currency)
return secret
def import_addrs(trade): def is_myaddr(self, address):
check_fund_status(trade.sell.currency, trade.sell.p2sh) # Handle differnt network prefixes
check_fund_status(trade.buy.currency, trade.buy.p2sh) if address[:1] == 'm':
status = self.bitcoinRPC.validateaddress(address)
else:
status = self.zcashRPC.validateaddress(address)
def check_p2sh(currency, address): logging.debug("Address status: ", status)
if currency == 'bitcoin':
print("Checking funds in Bitcoin p2sh")
return bitcoinRPC.check_funds(address)
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 not status['isvalid']:
if currency == 'bitcoin': raise ValueError("Invalid address: %s" % address)
print("Checking funds in Bitcoin p2sh") elif 'ismine' in status:
return bitcoinRPC.get_fund_status(address) status = status['ismine']
elif currency == 'zcash': # print("Address {0} is mine: {1}".format(address, status))
print("Checking funds in Zcash p2sh") return status
return zcashRPC.get_fund_status(address)
else:
raise ValueError("Currency not recognized: ", currency)
# TODO: function to calculate appropriate locktimes between chains def find_secret_from_fundtx(self, currency, p2sh, fundtx):
# def verify_p2sh(trade): if currency == 'bitcoin':
# htlc = create_htlc(trade.buy.currency, trade.buy.fulfiller, trade.buy.initiator, trade.commitment, trade.buy.locktime) secret = self.bitcoinRPC.find_secret(p2sh, fundtx)
# buyer_p2sh = htlc['p2sh'] elif currency == 'zcash':
# print("Buyer p2sh:", buyer_p2sh) secret = self.zcashRPC.find_secret(p2sh, fundtx)
# If the two p2sh match... else:
# if buyer_p2sh == trade.buy.p2sh: raise ValueError('Currency not recognized: %s' % currency)
# else: return secret
# print("Compiled p2sh for htlc does not match what seller sent.")
def create_htlc(currency, funder, redeemer, commitment, locktime): def import_addrs(self, trade):
if currency == 'bitcoin': self.check_fund_status(trade.sell.currency, trade.sell.p2sh)
sell_p2sh = bitcoinRPC.hashtimelockcontract(funder, redeemer, commitment, locktime) self.check_fund_status(trade.buy.currency, trade.buy.p2sh)
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): def check_p2sh(self, currency, address):
if currency == 'bitcoin': if currency == 'bitcoin':
txid = bitcoinRPC.fund_htlc(p2sh, amount) print("Checking funds in Bitcoin p2sh")
elif currency == 'zcash': return self.bitcoinRPC.check_funds(address)
txid = zcashRPC.fund_htlc(p2sh, amount) elif currency == 'zcash':
else: print("Checking funds in Zcash p2sh")
raise ValueError("Currency not recognized: ", currency) return self.zcashRPC.check_funds(address)
return txid else:
raise ValueError('Currency not recognized: %s' % currency)
def redeem_p2sh(contract, secret): def check_fund_status(self, currency, address):
currency = contract.currency if currency == 'bitcoin':
if currency == 'bitcoin': print("Checking funds in Bitcoin p2sh")
res = bitcoinRPC.redeem_contract(contract, secret) return self.bitcoinRPC.get_fund_status(address)
elif currency == 'zcash': elif currency == 'zcash':
res = zcashRPC.redeem_contract(contract, secret) print("Checking funds in Zcash p2sh")
else: return self.zcashRPC.get_fund_status(address)
raise ValueError("Currency not recognized: ", currency) else:
return res raise ValueError('Currency not recognized: %s' % currency)
def refund_contract(contract): # TODO: function to calculate appropriate locktimes between chains
currency = contract.currency # def verify_p2sh(trade):
if currency == 'bitcoin': # htlc = self.create_htlc(
res = bitcoinRPC.refund(contract) # trade.buy.currency, trade.buy.fulfiller,
elif currency == 'zcash': # trade.buy.initiator, trade.commitment,
res = zcashRPC.refund(contract) # trade.buy.locktime)
else: # buyer_p2sh = htlc['p2sh']
raise ValueError("Currency not recognized: ", currency) # print("Buyer p2sh:", buyer_p2sh)
return res # If the two p2sh match...
# if buyer_p2sh == trade.buy.p2sh:
# else:
# print("Compiled p2sh for htlc does not match what seller sent.")
def parse_secret(currency, txid): def create_htlc(self, currency, funder, redeemer, commitment, locktime):
if currency == 'bitcoin': if currency == 'bitcoin':
secret = bitcoinRPC.parse_secret(txid) sell_p2sh = self.bitcoinRPC.hashtimelockcontract(
elif currency == 'zcash': funder, redeemer, commitment, locktime)
secret = zcashRPC.parse_secret(txid) elif currency == 'zcash':
else: sell_p2sh = self.zcashRPC.hashtimelockcontract(
raise ValueError("Currency not recognized: ", currency) funder, redeemer, commitment, locktime)
return secret else:
raise ValueError('Currency not recognized: %s' % currency)
return sell_p2sh
def fund_contract(contract): def fund_htlc(self, currency, p2sh, amount):
txid = fund_htlc(contract.currency, contract.p2sh, contract.amount) if currency == 'bitcoin':
return txid txid = self.bitcoinRPC.fund_htlc(p2sh, amount)
elif currency == 'zcash':
txid = self.zcashRPC.fund_htlc(p2sh, amount)
else:
raise ValueError('Currency not recognized: %s' % currency)
return txid
def fund_sell_contract(trade): def fund_contract(self, contract):
sell = trade.sell txid = self.fund_htlc(
txid = fund_htlc(sell.currency, sell.p2sh, sell.amount) contract.currency, contract.p2sh, contract.amount)
setattr(trade.sell, 'fund_tx', txid) return txid
save(trade)
return txid
def create_sell_p2sh(trade, commitment, locktime): def fund_sell_contract(self, trade):
# CREATE SELL CONTRACT sell = trade.sell
sell = trade.sell txid = self.fund_htlc(sell.currency, sell.p2sh, sell.amount)
contract = create_htlc(sell.currency, sell.initiator, sell.fulfiller, commitment, locktime) setattr(trade.sell, 'fund_tx', txid)
print("sell contract", contract) utils.save(trade)
setattr(trade.sell, 'p2sh', contract['p2sh']) return txid
setattr(trade.sell, 'redeemScript', contract['redeemScript'])
setattr(trade.sell, 'redeemblocknum', contract['redeemblocknum'])
setattr(trade.buy, 'locktime', contract['locktime'])
save(trade)
def create_buy_p2sh(trade, commitment, locktime): def create_sell_p2sh(self, trade, commitment, locktime):
## CREATE BUY CONTRACT # CREATE SELL CONTRACT
buy = trade.buy sell = trade.sell
print("\nNow creating buy contract on the {0} blockchain where you will wait for the buyer to send funds...".format(buy.currency)) contract = self.create_htlc(sell.currency, sell.initiator,
buy_contract = create_htlc(buy.currency, buy.fulfiller, buy.initiator, commitment, locktime) sell.fulfiller, commitment, locktime)
print("Buy contract", buy_contract) print("sell contract", contract)
setattr(trade.sell, 'p2sh', contract['p2sh'])
setattr(trade.sell, 'redeemScript', contract['redeemScript'])
setattr(trade.sell, 'redeemblocknum', contract['redeemblocknum'])
setattr(trade.buy, 'locktime', contract['locktime'])
utils.save(trade)
setattr(trade.buy, 'p2sh', buy_contract['p2sh']) def create_buy_p2sh(self, trade, commitment, locktime):
setattr(trade.buy, 'redeemScript', buy_contract['redeemScript']) # CREATE BUY CONTRACT
setattr(trade.buy, 'redeemblocknum', buy_contract['redeemblocknum']) buy = trade.buy
setattr(trade.buy, 'locktime', buy_contract['locktime']) print("\nNow creating buy contract on the {0} blockchain where you "
print("\nNow contact the buyer and tell them to send funds to this p2sh: {0}\n".format(trade.buy.p2sh)) "will wait for the buyer to send funds...".format(buy.currency))
buy_contract = self.create_htlc(
buy.currency, buy.fulfiller, buy.initiator, commitment, locktime)
print("Buy contract", buy_contract)
save(trade) setattr(trade.buy, 'p2sh', buy_contract['p2sh'])
setattr(trade.buy, 'redeemScript', buy_contract['redeemScript'])
setattr(trade.buy, 'redeemblocknum', buy_contract['redeemblocknum'])
setattr(trade.buy, 'locktime', buy_contract['locktime'])
print("\nNow contact the buyer and tell them to send funds to this "
"p2sh: {0}\n".format(trade.buy.p2sh))
#### Main functions related to user flow from command line utils.save(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()
else:
# Seller redeems buyer's funded tx (contract in p2sh)
txs = redeem_p2sh(trade.buy, secret)
print("You have redeemed {0} {1}!".format(buy.amount, buy.currency))
return txs
def initialize_trade(tradeid, **kwargs): def redeem_p2sh(self, contract, secret):
trade = Trade() currency = contract.currency
conf = kwargs['conf'] if currency == 'bitcoin':
if conf == 'cli': res = self.bitcoinRPC.redeem_contract(contract, secret)
init_addrs = userInput.get_initiator_addresses() elif currency == 'zcash':
fulfill_addrs = userInput.get_fulfiller_addresses() res = self.zcashRPC.redeem_contract(contract, secret)
amounts = userInput.get_trade_amounts() else:
print("AMOUNTS", amounts) raise ValueError('Currency not recognized: %s' % currency)
else: return res
init_addrs = ADDRS[conf]['initiator']
fulfill_addrs = ADDRS[conf]['fulfiller']
amounts = ADDRS[conf]['amounts']
sell = amounts['sell'] def refund_contract(self, contract):
buy = amounts['buy'] currency = contract.currency
sell_currency = sell['currency'] if currency == 'bitcoin':
buy_currency = buy['currency'] res = self.bitcoinRPC.refund(contract)
sell['initiator'] = init_addrs[sell_currency] elif currency == 'zcash':
buy['initiator'] = init_addrs[buy_currency] res = self.zcashRPC.refund(contract)
sell['fulfiller'] = fulfill_addrs[sell_currency] else:
buy['fulfiller'] = fulfill_addrs[buy_currency] raise ValueError('Currency not recognized: %s', currency)
return res
# initializing contract classes with addresses, currencies, and amounts def parse_secret(self, currency, txid):
trade.sell = Contract(sell) if currency == 'bitcoin':
trade.buy = Contract(buy) secret = self.bitcoinRPC.parse_secret(txid)
print(trade.sell.__dict__) elif currency == 'zcash':
print(trade.buy.__dict__) secret = self.zcashRPC.parse_secret(txid)
return tradeid, trade else:
raise ValueError('Currency not recognized: %s', currency)
return secret
def seller_init(tradeid, trade, network): def seller_redeem_p2sh(self, trade, secret):
secret = generate_password() buy = trade.buy
db.save_secret(tradeid, secret) userInput.authorize_seller_redeem(buy)
print("Generated a secret preimage to lock funds. This will only be stored locally: {0}".format(secret))
hash_of_secret = sha256(secret) if trade.sell.get_status() == 'redeemed':
# TODO: Implement locktimes and mock block passage of time print("You already redeemed the funds and acquired "
sell_locktime = 20 "{0} {1}".format(buy.amount, buy.currency))
buy_locktime = 10 # Must be more than first tx exit()
print("Creating pay-to-script-hash for sell contract...") else:
# Seller redeems buyer's funded tx (contract in p2sh)
txs = self.redeem_p2sh(trade.buy, secret)
print("You have redeemed "
"{0} {1}!".format(buy.amount, buy.currency))
return txs
# create the p2sh addrs def initialize_trade(self, tradeid, **kwargs):
create_sell_p2sh(trade, hash_of_secret, sell_locktime) trade = Trade()
create_buy_p2sh(trade, hash_of_secret, buy_locktime) conf = kwargs['conf']
if conf == 'cli':
init_addrs = userInput.get_initiator_addresses()
fulfill_addrs = userInput.get_fulfiller_addresses()
amounts = userInput.get_trade_amounts()
print("AMOUNTS", amounts)
else:
init_addrs = ADDRS[conf]['initiator']
fulfill_addrs = ADDRS[conf]['fulfiller']
amounts = ADDRS[conf]['amounts']
trade.commitment = b2x(hash_of_secret) sell = amounts['sell']
print("TRADE after seller init: {0}".format(trade.toJSON())) buy = amounts['buy']
return trade sell_currency = sell['currency']
buy_currency = buy['currency']
sell['initiator'] = init_addrs[sell_currency]
buy['initiator'] = init_addrs[buy_currency]
sell['fulfiller'] = fulfill_addrs[sell_currency]
buy['fulfiller'] = fulfill_addrs[buy_currency]
# initializing contract classes with addresses, currencies, and amounts
trade.sell = Contract(sell)
trade.buy = Contract(buy)
print(trade.sell.__dict__)
print(trade.buy.__dict__)
return tradeid, trade
def seller_init(self, tradeid, trade, network):
db = DB()
secret = utils.generate_password()
db.save_secret(tradeid, secret)
print("\nGenerated a secret preimage to lock funds. This will only "
"be stored locally: {0}".format(secret))
hash_of_secret = utils.sha256(secret)
# TODO: Implement locktimes and mock block passage of time
sell_locktime = 20
buy_locktime = 10 # Must be more than first tx
print("Creating pay-to-script-hash for sell contract...")
# create the p2sh addrs
self.create_sell_p2sh(trade, hash_of_secret, sell_locktime)
self.create_buy_p2sh(trade, hash_of_secret, buy_locktime)
trade.commitment = utils.b2x(hash_of_secret)
print("TRADE after seller init {0}".format(trade.toJSON()))
return trade

View File

@ -1,36 +1,45 @@
import unittest import unittest
import xcat.cli as cli import xcat.cli as cli
import xcat.db as db import xcat.tests.utils as testutils
from xcat.tests.utils import mktrade from xcat.db import DB
from xcat.trades import Trade, Contract from xcat.protocol import Protocol
from xcat.trades import Trade # , Contract
class SimpleTestCase(unittest.TestCase): class SimpleTestCase(unittest.TestCase):
def setUp(self): def setUp(self):
self.trade = mktrade() self.trade = testutils.mktrade()
def test_exporttrade(self): def test_exporttrade(self):
self.__class__.hexstr = cli.exporttrade('test') self.__class__.hexstr = cli.exporttrade('test')
self.assertTrue(int(self.hexstr, 16)) self.assertTrue(int(self.hexstr, 16))
def test_importtrade(self): def test_importtrade(self):
trade = cli.importtrade('test', self.__class__.hexstr) # trade = cli.importtrade('test', self.__class__.hexstr)
pass
class CliTest(SimpleTestCase): class CliTest(SimpleTestCase):
def test_findtrade(self): def test_findtrade(self):
trade = cli.findtrade('test') # trade = cli.findtrade('test')
pass
def test_newtrade(self): def test_newtrade(self):
trade = cli.newtrade('new', conf='regtest') trade = cli.newtrade('new', conf='regtest')
self.assertTrue(isinstance(trade, Trade)) self.assertTrue(isinstance(trade, Trade))
def test_fundsell(self): def test_fundsell(self):
db = DB()
protocol = Protocol()
trade = db.get('new') trade = db.get('new')
status = cli.seller_check_status(trade) status = cli.seller_check_status(trade)
print("Trade status: {0}\n".format(status)) print("Trade status: {0}\n".format(status))
self.assertEqual(status, 'init') self.assertEqual(status, 'init')
fund_tx = cli.fund_sell_contract(trade)
fund_tx = protocol.fund_sell_contract(trade)
print("Sent fund_tx", fund_tx) print("Sent fund_tx", fund_tx)
# def test_fundbuy(self): # def test_fundbuy(self):

View File

@ -1,21 +1,25 @@
import xcat.database as db import unittest
import unittest, json
import xcat.trades as trades import xcat.trades as trades
from xcat.db import DB
class DatabaseTest(unittest.TestCase): class DatabaseTest(unittest.TestCase):
def setUp(self): def setUp(self):
self.data = {"sell": {"amount": 0.1, "redeemScript": "63a82003d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b68876a9147788b4511a25fba1092e67b307a6dcdb6da125d967022a04b17576a914c7043e62a7391596116f54f6a64c8548e97d3fd96888ac", "redeemblocknum": 1066, "currency": "bitcoin", "initiator": "myfFr5twPYNwgeXyjCmGcrzXtCmfmWXKYp", "p2sh": "2MuYSQ1uQ4CJg5Y5QL2vMmVPHNJ2KT5aJ6f", "fulfiller": "mrQzUGU1dwsWRx5gsKKSDPNtrsP65vCA3Z", "fund_tx": "5c5e91a89a08b2d6698f50c9fd9bb2fa22da6c74e226c3dd63d59511566a2fdb"}, "buy": {"amount": 0.2, "redeemScript": "63a82003d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b68876a9143ea29256c9d2888ca23de42a8b8e69ca2ec235b167023f0db17576a914c5acca6ef39c843c7a9c3ad01b2da95fe2edf5ba6888ac", "redeemblocknum": 3391, "currency": "zcash", "locktime": 10, "initiator": "tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ", "p2sh": "t2HP59RpfR34nBCWH4VVD497tkc2ikzgniP", "fulfiller": "tmTjZSg4pX2Us6V5HttiwFZwj464fD2ZgpY"}, "commitment": "03d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b6"} self.data = {"sell": {"amount": 0.1, "redeemScript": "63a82003d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b68876a9147788b4511a25fba1092e67b307a6dcdb6da125d967022a04b17576a914c7043e62a7391596116f54f6a64c8548e97d3fd96888ac", "redeemblocknum": 1066, "currency": "bitcoin", "initiator": "myfFr5twPYNwgeXyjCmGcrzXtCmfmWXKYp", "p2sh": "2MuYSQ1uQ4CJg5Y5QL2vMmVPHNJ2KT5aJ6f", "fulfiller": "mrQzUGU1dwsWRx5gsKKSDPNtrsP65vCA3Z", "fund_tx": "5c5e91a89a08b2d6698f50c9fd9bb2fa22da6c74e226c3dd63d59511566a2fdb"}, "buy": {"amount": 0.2, "redeemScript": "63a82003d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b68876a9143ea29256c9d2888ca23de42a8b8e69ca2ec235b167023f0db17576a914c5acca6ef39c843c7a9c3ad01b2da95fe2edf5ba6888ac", "redeemblocknum": 3391, "currency": "zcash", "locktime": 10, "initiator": "tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ", "p2sh": "t2HP59RpfR34nBCWH4VVD497tkc2ikzgniP", "fulfiller": "tmTjZSg4pX2Us6V5HttiwFZwj464fD2ZgpY"}, "commitment": "03d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b6"}
self.sell = trades.Contract(self.data['sell']) self.sell = trades.Contract(self.data['sell'])
def test_create(self): def test_create(self):
db = DB()
sell = trades.Contract(self.data['sell']) sell = trades.Contract(self.data['sell'])
buy = trades.Contract(self.data['buy']) buy = trades.Contract(self.data['buy'])
trade = trades.Trade(sell, buy, commitment=self.data['commitment']) trade = trades.Trade(sell, buy, commitment=self.data['commitment'])
db.create(trade, 'test') db.create(trade, 'test')
def test_get(self): def test_get(self):
trade = db.get('test') # trade = db.get('test')
print("Trade") print("Trade")
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -0,0 +1,133 @@
import unittest
import unittest.mock as mock
from xcat.bitcoinRPC import bitcoinProxy
import logging
@mock.patch('xcat.bitcoinRPC.bitcoin.rpc')
class TestBitcoinProxy(unittest.TestCase):
"""Test case for the bitcoinProxy class."""
def setUp(self):
logging.disable(logging.CRITICAL)
@mock.patch('xcat.bitcoinRPC.bitcoin.SelectParams')
def test_init_with_testnet(self, mock_SP, mock_rpc):
"""Test bitcoinProxy.__init__"""
proxy = bitcoinProxy(network='testnet')
mock_rpc.Proxy.assert_called_with(timeout=900)
mock_SP.assert_called_with('testnet')
self.assertIsInstance(proxy, bitcoinProxy)
@mock.patch('xcat.bitcoinRPC.bitcoin.SelectParams')
def test_init_with_no_network(self, mock_SP, mock_rpc):
"""Test bitcoinProxy.__init__"""
proxy = bitcoinProxy()
mock_rpc.Proxy.assert_called_with(timeout=900)
mock_SP.assert_called_with('regtest')
self.assertIsInstance(proxy, bitcoinProxy)
def test_init_with_invalid(self, mock_rpc):
"""Test bitcoinProxy.__init__"""
with self.assertRaises(ValueError) as context:
proxy = bitcoinProxy(network='invalid input')
self.assertIsNone(proxy)
self.assertTrue(
'Allowed networks are regtest, testnet, mainnet.'
in str(context.exception))
with self.assertRaises(ValueError) as context_two:
proxy = bitcoinProxy(network=819.3)
self.assertIsNone(proxy)
self.assertTrue(
'Allowed networks are regtest, testnet, mainnet.'
in str(context_two.exception))
def test_init_with_invalid_timeout(self, mock_rpc):
"""Test bitcoinProxy.__init__"""
with self.assertRaises(ValueError) as context:
proxy = bitcoinProxy(timeout='invalid input')
self.assertIsNone(proxy)
self.assertTrue(
'Timeout should be a positive integer.'
in str(context.exception))
with self.assertRaises(ValueError) as context_two:
proxy = bitcoinProxy(timeout=-381)
self.assertIsNone(proxy)
self.assertTrue(
'Timeout should be a positive integer.'
in str(context_two.exception))
def test_validateaddress(self, mock_rpc):
pass
def test_find_secret(self, mock_rpc):
pass
def test_parse_secret(self, mock_rpc):
pass
def test_get_keys(self, mock_rpc):
pass
def test_privkey(self, mock_rpc):
pass
def test_hashtimelockcontract(self, mock_rpc):
pass
def test_fund_htlc(self, mock_rpc):
pass
def test_check_funds(self, mock_rpc):
pass
def test_get_fund_status(self, mock_rpc):
pass
def test_search_p2sh(self, mock_rpc):
pass
def test_get_tx_details(self, mock_rpc):
pass
def test_redeem_contract(self, mock_rpc):
pass
def test_redeem(self, mock_rpc):
pass
def test_refund(self, mock_rpc):
pass
def test_parse_script(self, mock_rpc):
pass
def test_find_redeemblocknum(self, mock_rpc):
pass
def test_find_redeemAddr(self, mock_rpc):
pass
def test_find_refundAddr(self, mock_rpc):
pass
def test_find_transaction_to_address(self, mock_rpc):
pass
def test_new_bitcoin_addr(self, mock_rpc):
pass
def test_generate(self, mock_rpc):
pass

116
xcat/tests/unit/test_cli.py Normal file
View File

@ -0,0 +1,116 @@
import unittest
import unittest.mock as mock
import xcat.cli as cli
# from xcat.tests.utils import test_trade
# from xcat.trades import Trade
class TestCLI(unittest.TestCase):
@mock.patch('xcat.cli.DB')
@mock.patch('xcat.cli.utils')
def test_save_state(self, mock_utils, mock_db):
cli.save_state('fake_trade', 'fake_id')
mock_utils.save.assert_called_with('fake_trade')
mock_db.return_value.create.assert_called_with('fake_trade', 'fake_id')
def test_checkSellStatus(self):
pass
def test_buyer_check_status(self):
pass
def test_seller_check_status(self):
pass
def test_checkBuyStatus(self):
pass
def test_importtrade(self):
pass
def test_wormhole_importtrade(self):
pass
def test_exporttrade(self):
pass
def test_findtrade(self):
pass
@mock.patch('xcat.cli.Protocol')
def test_find_role_test(self, mock_protocol):
mock_protocol().is_myaddr = lambda k: k == 'me'
test_contract = mock.MagicMock()
test_contract.initiator = 'me'
test_contract.fulfiller = 'me'
res = cli.find_role(test_contract)
self.assertEqual(res, 'test')
@mock.patch('xcat.cli.Protocol')
def test_find_role_initiator(self, mock_protocol):
mock_protocol().is_myaddr = lambda k: k == 'me'
test_contract = mock.MagicMock()
test_contract.initiator = 'me'
test_contract.fulfiller = 'you'
res = cli.find_role(test_contract)
self.assertEqual(res, 'initiator')
@mock.patch('xcat.cli.Protocol')
def test_find_role_fulfiller(self, mock_protocol):
mock_protocol().is_myaddr = lambda k: k == 'me'
test_contract = mock.MagicMock()
test_contract.initiator = 'you'
test_contract.fulfiller = 'me'
res = cli.find_role(test_contract)
self.assertEqual(res, 'fulfiller')
@mock.patch('xcat.cli.Protocol')
def test_find_role_error(self, mock_protocol):
mock_protocol().is_myaddr = lambda k: k == 'me'
test_contract = mock.MagicMock()
test_contract.initiator = 'you'
test_contract.fulfiller = 'you'
with self.assertRaises(ValueError) as context:
cli.find_role(test_contract)
self.assertTrue(
'You are not a participant in this contract.'
in str(context.exception))
def test_checktrade(self):
pass
def test_newtrade(self):
pass
def test_listtrades(self):
pass
def test_fundsell(self):
pass
def test_fundbuy(self):
pass
def test_seller_redeem(self):
pass
def test_buyer_redeem(self):
pass
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,98 @@
import unittest
import unittest.mock as mock
import json
import xcat.db as db
import xcat.tests.utils as utils
class TestDB(unittest.TestCase):
@mock.patch('xcat.db.plyvel')
def setUp(self, mock_plyvel):
self.db = db.DB()
def test_init(self):
self.assertIsInstance(self.db.db, mock.Mock)
self.assertIsInstance(self.db.preimageDB, mock.Mock)
def test_create_with_dict(self):
test_id = 'test trade id'
self.db.create(utils.test_trade_dict, test_id)
self.db.db.put.assert_called_with(
str.encode(test_id),
str.encode(str(utils.test_trade)))
def test_create_with_trade(self):
test_id = 'test trade id'
self.db.create(utils.test_trade, test_id)
self.db.db.put.assert_called_with(
str.encode(test_id),
str.encode(json.dumps(utils.test_trade_dict,
sort_keys=True,
indent=4)))
def test_create_with_error(self):
with self.assertRaises(ValueError) as context:
self.db.create('this is not valid input', 'trade_id')
self.assertTrue(
'Expected dictionary or Trade object'
in str(context.exception))
def test_createByFundtx_with_dict(self):
self.db.createByFundtx(utils.test_trade_dict)
self.db.db.put.assert_called_with(
str.encode('5c5e91a89a08b2d6698f50c9fd9bb2fa22da6c74e226c3dd63d'
'59511566a2fdb'),
str.encode(str(utils.test_trade)))
def test_createByFundtx_with_trade(self):
self.db.createByFundtx(utils.test_trade)
self.db.db.put.assert_called_with(
str.encode('5c5e91a89a08b2d6698f50c9fd9bb2fa22da6c74e226c3dd63d'
'59511566a2fdb'),
str.encode(json.dumps(utils.test_trade_dict,
sort_keys=True,
indent=4)))
def test_createByFundtx_with_error(self):
with self.assertRaises(ValueError) as context:
self.db.createByFundtx('this is not valid input')
self.assertTrue(
'Expected dictionary or Trade object'
in str(context.exception))
def test_get(self):
self.db.db.get.return_value = str.encode(utils.test_trade.toJSON())
trade = self.db.get('test')
self.assertEqual(trade, utils.test_trade)
def test_save_secret(self):
self.db.save_secret('my life', 'I like black liquorice')
self.db.preimageDB.put.assert_called_with(
str.encode('my life'),
str.encode('I like black liquorice'))
def test_get_secret(self):
self.db.preimageDB.get.return_value = str.encode(
'I like black liquorice')
secret = self.db.get_secret('my life')
self.assertEqual(secret, 'I like black liquorice')
def test_dump(self):
pass
def test_print_entries(self):
pass

View File

@ -0,0 +1,133 @@
import unittest
import unittest.mock as mock
from xcat.zcashRPC import zcashProxy
import logging
@mock.patch('xcat.zcashRPC.zcash.rpc')
class TestBitcoinProxy(unittest.TestCase):
"""Test case for the zcashProxy class."""
def setUp(self):
logging.disable(logging.CRITICAL)
@mock.patch('xcat.zcashRPC.zcash.SelectParams')
def test_init_with_testnet(self, mock_SP, mock_rpc):
"""Test zcashProxy.__init__"""
proxy = zcashProxy(network='testnet')
mock_rpc.Proxy.assert_called_with(timeout=900)
mock_SP.assert_called_with('testnet')
self.assertIsInstance(proxy, zcashProxy)
@mock.patch('xcat.zcashRPC.zcash.SelectParams')
def test_init_with_no_network(self, mock_SP, mock_rpc):
"""Test zcashProxy.__init__"""
proxy = zcashProxy()
mock_rpc.Proxy.assert_called_with(timeout=900)
mock_SP.assert_called_with('regtest')
self.assertIsInstance(proxy, zcashProxy)
def test_init_with_invalid(self, mock_rpc):
"""Test zcashProxy.__init__"""
with self.assertRaises(ValueError) as context:
proxy = zcashProxy(network='invalid input')
self.assertIsNone(proxy)
self.assertTrue(
'Allowed networks are regtest, testnet, mainnet.'
in str(context.exception))
with self.assertRaises(ValueError) as context_two:
proxy = zcashProxy(network=819.3)
self.assertIsNone(proxy)
self.assertTrue(
'Allowed networks are regtest, testnet, mainnet.'
in str(context_two.exception))
def test_init_with_invalid_timeout(self, mock_rpc):
"""Test zcashProxy.__init__"""
with self.assertRaises(ValueError) as context:
proxy = zcashProxy(timeout='invalid input')
self.assertIsNone(proxy)
self.assertTrue(
'Timeout should be a positive integer.'
in str(context.exception))
with self.assertRaises(ValueError) as context_two:
proxy = zcashProxy(timeout=-381)
self.assertIsNone(proxy)
self.assertTrue(
'Timeout should be a positive integer.'
in str(context_two.exception))
def test_validateaddress(self, mock_rpc):
pass
def test_find_secret(self, mock_rpc):
pass
def test_parse_secret(self, mock_rpc):
pass
def test_get_keys(self, mock_rpc):
pass
def test_privkey(self, mock_rpc):
pass
def test_hashtimelockcontract(self, mock_rpc):
pass
def test_fund_htlc(self, mock_rpc):
pass
def test_check_funds(self, mock_rpc):
pass
def test_get_fund_status(self, mock_rpc):
pass
def test_search_p2sh(self, mock_rpc):
pass
def test_get_tx_details(self, mock_rpc):
pass
def test_redeem_contract(self, mock_rpc):
pass
def test_redeem(self, mock_rpc):
pass
def test_refund(self, mock_rpc):
pass
def test_parse_script(self, mock_rpc):
pass
def test_find_redeemblocknum(self, mock_rpc):
pass
def test_find_redeemAddr(self, mock_rpc):
pass
def test_find_refundAddr(self, mock_rpc):
pass
def test_find_transaction_to_address(self, mock_rpc):
pass
def test_new_bitcoin_addr(self, mock_rpc):
pass
def test_generate(self, mock_rpc):
pass

View File

@ -1,8 +1,36 @@
import xcat.db as db from xcat.db import DB
from xcat.trades import Contract, Trade
test_trade_dict = {
"sell": {
"amount": 3.5,
"redeemScript": "63a82003d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b68876a9147788b4511a25fba1092e67b307a6dcdb6da125d967022a04b17576a914c7043e62a7391596116f54f6a64c8548e97d3fd96888ac",
"redeemblocknum": 1066,
"currency": "bitcoin",
"initiator": "myfFr5twPYNwgeXyjCmGcrzXtCmfmWXKYp",
"p2sh": "2MuYSQ1uQ4CJg5Y5QL2vMmVPHNJ2KT5aJ6f",
"fulfiller": "mrQzUGU1dwsWRx5gsKKSDPNtrsP65vCA3Z",
"fund_tx": "5c5e91a89a08b2d6698f50c9fd9bb2fa22da6c74e226c3dd63d59511566a2fdb"},
"buy": {
"amount": 1.2,
"redeemScript": "63a82003d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b68876a9143ea29256c9d2888ca23de42a8b8e69ca2ec235b167023f0db17576a914c5acca6ef39c843c7a9c3ad01b2da95fe2edf5ba6888ac",
"redeemblocknum": 3391,
"currency": "zcash",
"locktime": 10,
"initiator": "tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ",
"p2sh": "t2HP59RpfR34nBCWH4VVD497tkc2ikzgniP",
"fulfiller": "tmTjZSg4pX2Us6V5HttiwFZwj464fD2ZgpY"},
"commitment": "03d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b6"}
test_sell_contract = Contract(test_trade_dict['sell'])
test_buy_contract = Contract(test_trade_dict['buy'])
test_trade = Trade(sell=test_sell_contract,
buy=test_buy_contract,
commitment=test_trade_dict['commitment'])
test_trade = {"sell": {"amount": 3.5, "redeemScript": "63a82003d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b68876a9147788b4511a25fba1092e67b307a6dcdb6da125d967022a04b17576a914c7043e62a7391596116f54f6a64c8548e97d3fd96888ac", "redeemblocknum": 1066, "currency": "bitcoin", "initiator": "myfFr5twPYNwgeXyjCmGcrzXtCmfmWXKYp", "p2sh": "2MuYSQ1uQ4CJg5Y5QL2vMmVPHNJ2KT5aJ6f", "fulfiller": "mrQzUGU1dwsWRx5gsKKSDPNtrsP65vCA3Z", "fund_tx": "5c5e91a89a08b2d6698f50c9fd9bb2fa22da6c74e226c3dd63d59511566a2fdb"}, "buy": {"amount": 1.2, "redeemScript": "63a82003d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b68876a9143ea29256c9d2888ca23de42a8b8e69ca2ec235b167023f0db17576a914c5acca6ef39c843c7a9c3ad01b2da95fe2edf5ba6888ac", "redeemblocknum": 3391, "currency": "zcash", "locktime": 10, "initiator": "tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ", "p2sh": "t2HP59RpfR34nBCWH4VVD497tkc2ikzgniP", "fulfiller": "tmTjZSg4pX2Us6V5HttiwFZwj464fD2ZgpY"}, "commitment": "03d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b6"}
def mktrade(): def mktrade():
db = DB()
db.create(test_trade, 'test') db.create(test_trade, 'test')
trade = db.get('test') trade = db.get('test')
return trade return trade

View File

@ -1,21 +1,56 @@
import json import json
class Trade(object):
def __init__(self, sell=None, buy=None, commitment=None): class Trade():
'''Create a new trade with a sell contract and buy contract across two chains''' def __init__(self, sell=None, buy=None, commitment=None,
self.sell = sell fromJSON=None, fromDict=None):
self.buy = buy '''Create a new trade with buy and sell contracts across two chains'''
self.commitment = commitment
if fromJSON is not None and fromDict is None:
if isinstance(fromJSON, str):
fromDict = json.loads(fromJSON)
else:
raise ValueError('Expected json string')
if fromDict is not None:
self.sell = Contract(fromDict['sell'])
self.buy = Contract(fromDict['buy'])
self.commitment = fromDict['commitment']
else:
self.sell = sell
self.buy = buy
self.commitment = commitment
def toJSON(self): def toJSON(self):
return json.dumps(self, default=lambda o: o.__dict__, return json.dumps(
sort_keys=True, indent=4) self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
def __str__(self):
return self.toJSON()
def __repr__(self):
return 'Trade:\n{0} {1} from {2}\nfor\n{3} {4} from {5}'.format(
self.sell.amount,
self.sell.currency,
self.sell.initiator,
self.buy.amount,
self.buy.currency,
self.buy.initiator)
def __eq__(self, other):
return (self.sell == other.sell
and self.buy == other.buy
and self.commitment == other.commitment)
class Contract():
allowed = ('fulfiller', 'initiator', 'currency', 'p2sh', 'amount',
'fund_tx', 'redeem_tx', 'secret', 'redeemScript',
'redeemblocknum', 'locktime')
class Contract(object):
def __init__(self, data): def __init__(self, data):
allowed = ('fulfiller', 'initiator', 'currency', 'p2sh', 'amount', 'fund_tx', 'redeem_tx', 'secret', 'redeemScript', 'redeemblocknum', 'locktime')
for key in data: for key in data:
if key in allowed: if key in Contract.allowed:
setattr(self, key, data[key]) setattr(self, key, data[key])
def get_status(self): def get_status(self):
@ -28,3 +63,14 @@ class Contract(object):
return 'funded' return 'funded'
else: else:
return 'empty' return 'empty'
def __eq__(self, other):
for key in Contract.allowed:
if key in self.__dict__:
if key not in other.__dict__:
return False
if self.__dict__[key] != other.__dict__[key]:
return False
if key in other.__dict__ and key not in self.__dict__:
return False
return True

View File

@ -1,17 +1,20 @@
from xcat.utils import * # from xcat.utils import *
from xcat.db import * # from xcat.db import *
from xcat.bitcoinRPC import bitcoinProxy from xcat.bitcoinRPC import bitcoinProxy
from xcat.zcashRPC import zcashProxy from xcat.zcashRPC import zcashProxy
from xcat.xcatconf import * # from xcat.xcatconf import *
def enter_trade_id(): def enter_trade_id():
tradeid = input("Enter a unique identifier for this trade: ") tradeid = input("Enter a unique identifier for this trade: ")
return tradeid return tradeid
def get_trade_amounts(): def get_trade_amounts():
amounts = {} amounts = {}
sell_currency = input("Which currency would you like to trade out of (bitcoin or zcash)? ") sell_currency = input("Which currency would you like to trade out of "
if sell_currency == '' or sell_currency == 'bitcoin' : "(bitcoin or zcash)? ")
if sell_currency == '' or sell_currency == 'bitcoin':
sell_currency = 'bitcoin' sell_currency = 'bitcoin'
buy_currency = 'zcash' buy_currency = 'zcash'
elif sell_currency == 'zcash': elif sell_currency == 'zcash':
@ -20,11 +23,13 @@ def get_trade_amounts():
else: else:
raise ValueError('Mistyped or unspported cryptocurrency pair') raise ValueError('Mistyped or unspported cryptocurrency pair')
print(sell_currency) print(sell_currency)
sell_amt = input("How much {0} do you want to sell? ".format(sell_currency)) sell_amt = input("How much {0} do you "
"want to sell? ".format(sell_currency))
if sell_amt == '': if sell_amt == '':
sell_amt = 0.01 sell_amt = 0.01
print(sell_amt) print(sell_amt)
buy_amt = input("How much {0} do you want to receive in exchange? ".format(buy_currency)) buy_amt = input("How much {0} do you "
"want to receive in exchange? ".format(buy_currency))
if buy_amt == '': if buy_amt == '':
buy_amt = 0.02 buy_amt = 0.02
print(buy_amt) print(buy_amt)
@ -34,40 +39,66 @@ def get_trade_amounts():
amounts['buy'] = buy amounts['buy'] = buy
return amounts return amounts
def authorize_fund_sell(htlcTrade): def authorize_fund_sell(htlcTrade):
print('To complete your sell, send {0} {1} to this p2sh: {2}'.format(htlcTrade.sell.amount, htlcTrade.sell.currency, htlcTrade.sell.p2sh)) print('To complete your sell, send {0} {1} to this p2sh: '
response = input("Type 'enter' to allow this program to send funds on your behalf.") '{2}'.format(htlcTrade.sell.amount,
htlcTrade.sell.currency,
htlcTrade.sell.p2sh))
input("Type 'enter' to allow this program to send funds on your behalf.")
def get_initiator_addresses(): def get_initiator_addresses():
bitcoinRPC = bitcoinProxy() bitcoinRPC = bitcoinProxy()
zcashRPC = zcashProxy() zcashRPC = zcashProxy()
btc_addr = input("Enter your bitcoin address or press enter to generate one: ") btc_addr = input("Enter your bitcoin address "
"or press enter to generate one: ")
btc_addr = bitcoinRPC.new_bitcoin_addr() btc_addr = bitcoinRPC.new_bitcoin_addr()
print(btc_addr) print(btc_addr)
zec_addr = input("Enter your zcash address or press enter to generate one: ") zec_addr = input("Enter your zcash address "
"or press enter to generate one: ")
zec_addr = zcashRPC.new_zcash_addr() zec_addr = zcashRPC.new_zcash_addr()
print(zec_addr) print(zec_addr)
addresses = {'bitcoin': btc_addr, 'zcash': zec_addr} addresses = {'bitcoin': btc_addr, 'zcash': zec_addr}
return addresses return addresses
def get_fulfiller_addresses(): def get_fulfiller_addresses():
btc_addr = input("Enter the bitcoin address of the party you want to trade with: ") btc_addr = input("Enter the bitcoin address of "
"the party you want to trade with: ")
if btc_addr == '': if btc_addr == '':
btc_addr = "mvc56qCEVj6p57xZ5URNC3v7qbatudHQ9b" # regtest btc_addr = "mvc56qCEVj6p57xZ5URNC3v7qbatudHQ9b" # regtest
print(btc_addr) print(btc_addr)
zec_addr = input("Enter the zcash address of the party you want to trade with: ")
zec_addr = input("Enter the zcash address of "
"the party you want to trade with: ")
if zec_addr == '': if zec_addr == '':
zec_addr = "tmTF7LMLjvEsGdcepWPUsh4vgJNrKMWwEyc" # regtest zec_addr = "tmTF7LMLjvEsGdcepWPUsh4vgJNrKMWwEyc" # regtest
print(zec_addr) print(zec_addr)
addresses = {'bitcoin': btc_addr, 'zcash': zec_addr} addresses = {'bitcoin': btc_addr, 'zcash': zec_addr}
return addresses return addresses
def authorize_buyer_fulfill(sell_p2sh_balance, sell_currency, buy_p2sh_balance, buy_currency):
input("The seller's p2sh is funded with {0} {1}, type 'enter' if this is the amount you want to buy in {1}.".format(sell_p2sh_balance, sell_currency)) def authorize_buyer_fulfill(sell_p2sh_balance, sell_currency,
input("You have not send funds to the contract to buy {1} (requested amount: {0}), type 'enter' to allow this program to send the agreed upon funds on your behalf.".format(buy_p2sh_balance, buy_currency)) buy_p2sh_balance, buy_currency):
input("The seller's p2sh is funded with {0} {1}, "
"type 'enter' if this is the amount you want to buy "
"in {1}.".format(sell_p2sh_balance, sell_currency))
input("You have not send funds to the contract to buy {1} "
"(requested amount: {0}), type 'enter' to allow this program "
"to send the agreed upon funds on your behalf"
".".format(buy_p2sh_balance, buy_currency))
def authorize_seller_redeem(buy): def authorize_seller_redeem(buy):
input("Buyer funded the contract where you offered to buy {0}, type 'enter' to redeem {1} {0} from {2}.".format(buy.currency, buy.amount, buy.p2sh)) input("Buyer funded the contract where you offered to buy {0}, "
"type 'enter' to redeem {1} {0} from "
"{2}.".format(buy.currency, buy.amount, buy.p2sh))
def authorize_buyer_redeem(trade): def authorize_buyer_redeem(trade):
input("Seller funded the contract where you paid them in {0} to buy {1}, type 'enter' to redeem {2} {1} from {3}.".format(trade.buy.currency, trade.sell.currency, trade.sell.amount, trade.sell.p2sh)) input("Seller funded the contract where you paid them in {0} "
"to buy {1}, type 'enter' to redeem {2} {1} from "
"{3}.".format(trade.buy.currency, trade.sell.currency,
trade.sell.amount, trade.sell.p2sh))

View File

@ -1,80 +1,102 @@
import hashlib, json, random, binascii import hashlib
import xcat.trades as trades import json
import random
import binascii
import os import os
import xcat.trades as trades
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) ROOT_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
############################################ ############################################
########### Data conversion utils ########## ########### Data conversion utils ##########
############################################ ############################################
def b(string): def b(string):
"""Convert a string to bytes""" """Convert a string to bytes"""
return str.encode(string) return str.encode(string)
def x(h): def x(h):
"""Convert a hex string to bytes""" """Convert a hex string to bytes"""
return binascii.unhexlify(h.encode('utf8')) return binascii.unhexlify(h.encode('utf8'))
def b2x(b): def b2x(b):
"""Convert bytes to a hex string""" """Convert bytes to a hex string"""
return binascii.hexlify(b).decode('utf8') return binascii.hexlify(b).decode('utf8')
def x2s(hexstring): def x2s(hexstring):
"""Convert hex to a utf-8 string""" """Convert hex to a utf-8 string"""
return binascii.unhexlify(hexstring).decode('utf-8') return binascii.unhexlify(hexstring).decode('utf-8')
def s2x(string): def s2x(string):
"""Convert a utf-8 string to hex""" """Convert a utf-8 string to hex"""
return b2x(b(string)) return b2x(b(string))
def hex2dict(hexstr): def hex2dict(hexstr):
jsonstr = x2s(hexstr) jsonstr = x2s(hexstr)
print(hexstr['fund_tx']) print(hexstr['fund_tx'])
print(jsonstr) print(jsonstr)
return json.loads(jsonstr) return json.loads(jsonstr)
def jsonformat(trade): def jsonformat(trade):
return { return {
'sell': trade.sell.__dict__, 'sell': trade.sell.__dict__,
'buy': trade.buyContract.__dict__ 'buy': trade.buyContract.__dict__
} }
############################################ ############################################
########### Preimage utils ################# ########### Preimage utils #################
############################################ ############################################
def generate_password(): def generate_password():
s = "1234567890abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ" s = ("1234567890abcdefghijklmnopqrstuvwxyz"
"01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ")
passlen = 32 passlen = 32
p = "".join(random.sample(s,passlen)) p = "".join(random.sample(s, passlen))
return p return p
def sha256(secret): def sha256(secret):
preimage = secret.encode('utf8') preimage = secret.encode('utf8')
h = hashlib.sha256(preimage).digest() h = hashlib.sha256(preimage).digest()
return h return h
############################################ ############################################
######## Error handling for CLI ############ ######## Error handling for CLI ############
############################################ ############################################
def throw(err): def throw(err):
print(err) print(err)
exit() exit()
############################################# #############################################
######### xcat.json temp file ############# ######### xcat.json temp file #############
############################################# #############################################
tmp_dir = os.path.join(ROOT_DIR, '.tmp') tmp_dir = os.path.join(ROOT_DIR, '.tmp')
if not os.path.exists(tmp_dir): if not os.path.exists(tmp_dir):
os.makedirs(tmp_dir) os.makedirs(tmp_dir)
xcatjson = os.path.join(tmp_dir, 'xcat.json') xcatjson = os.path.join(tmp_dir, 'xcat.json')
def save_trade(trade): def save_trade(trade):
with open(xcatjson, 'w+') as outfile: with open(xcatjson, 'w+') as outfile:
json.dump(trade, outfile) json.dump(trade, outfile)
def get_trade(): def get_trade():
with open(xcatjson) as data_file: with open(xcatjson) as data_file:
xcatdb = json.load(data_file) xcatdb = json.load(data_file)
@ -83,6 +105,7 @@ def get_trade():
trade = trades.Trade(sell, buy, commitment=xcatdb['commitment']) trade = trades.Trade(sell, buy, commitment=xcatdb['commitment'])
return trade return trade
def erase_trade(): def erase_trade():
try: try:
with open(xcatjson, 'w') as outfile: with open(xcatjson, 'w') as outfile:
@ -90,15 +113,17 @@ def erase_trade():
except: except:
pass pass
def save(trade): def save(trade):
# print("Saving trade") # print("Saving trade")
trade = { trade = {
'sell': trade.sell.__dict__, 'sell': trade.sell.__dict__,
'buy': trade.buy.__dict__, 'buy': trade.buy.__dict__,
'commitment': trade.commitment 'commitment': trade.commitment
} }
save_trade(trade) save_trade(trade)
# Remove tmp files when trade is complete # Remove tmp files when trade is complete
def cleanup(tradeid): def cleanup(tradeid):
try: try:

View File

@ -17,11 +17,9 @@ ADDRS = {
"zcash": "tmTF7LMLjvEsGdcepWPUsh4vgJNrKMWwEyc" "zcash": "tmTF7LMLjvEsGdcepWPUsh4vgJNrKMWwEyc"
}, },
"fulfiller": { "fulfiller": {
"bitcoin": "mn2boR7rYq9DaAWWrVN5MazHKFyf7UhdyU", "bitcoin": "mm2smEJjRN4xoijEfpb5XvYd8e3EYWezom",
"zcash": "tmErB22A1G74aq32aAh5AoqgQSJsAAAdT2p" "zcash": "tmPwPdceaJAHQn7UiRCVnJ5tXBXHVqWMkis"
}, },
"amounts": {'buy': {'currency': 'zcash', 'amount': 0.02}, 'sell': {'currency': 'bitcoin', 'amount': 0.01}} "amounts": {'buy': {'currency': 'zcash', 'amount': 0.02}, 'sell': {'currency': 'bitcoin', 'amount': 0.01}}
} }
} }
NETWORK = 'testnet'

View File

@ -1,29 +1,40 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import sys import sys
import zcash
import zcash.rpc
# import logging
# from zcash import SelectParams
from zcash.core import b2x, lx, x, COIN
from zcash.core import CMutableTransaction, CMutableTxOut, CMutableTxIn
from zcash.core.script import CScript, OP_DUP, OP_IF, OP_ELSE, OP_ENDIF
from zcash.core.script import OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG
from zcash.core.script import SignatureHash, SIGHASH_ALL, OP_FALSE, OP_DROP
from zcash.core.script import OP_CHECKLOCKTIMEVERIFY, OP_SHA256, OP_TRUE
from zcash.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH
from zcash.wallet import CBitcoinAddress, P2PKHBitcoinAddress
from zcash.wallet import P2SHBitcoinAddress
from xcat.utils import x2s
if sys.version_info.major < 3: if sys.version_info.major < 3:
sys.stderr.write('Sorry, Python 3.x required by this example.\n') sys.stderr.write('Sorry, Python 3.x required by this example.\n')
sys.exit(1) sys.exit(1)
import zcash FEE = 0.001 * COIN
import zcash.rpc
from zcash import SelectParams
from zcash.core import b2x, lx, x, b2lx, COIN, COutPoint, CMutableTxOut, CMutableTxIn, CMutableTransaction, Hash160
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
FEE = 0.001*COIN
class zcashProxy(): class zcashProxy():
def __init__(self, network='regtest', timeout=900): def __init__(self, network='regtest', timeout=900):
if network not in ['testnet', 'mainnet', 'regtest']:
raise ValueError('Allowed networks are regtest, testnet, mainnet.')
if not isinstance(timeout, int) or timeout < 1:
raise ValueError('Timeout should be a positive integer.')
self.network = network self.network = network
self.timeout = timeout self.timeout = timeout
SelectParams(self.network) zcash.SelectParams(self.network)
self.zcashd = zcash.rpc.Proxy(timeout=self.timeout) self.zcashd = zcash.rpc.Proxy(timeout=self.timeout)
def validateaddress(self, addr): def validateaddress(self, addr):
@ -47,23 +58,34 @@ class zcashProxy():
print("Current blocknum on Zcash: ", blocknum) print("Current blocknum on Zcash: ", blocknum)
redeemblocknum = blocknum + locktime redeemblocknum = blocknum + locktime
print("Redeemblocknum on Zcash: ", redeemblocknum) 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) # can rm op_dup and op_hash160 if you replace addrs with pubkeys
zec_redeemScript = CScript([OP_IF, OP_SHA256, commitment, OP_EQUALVERIFY,OP_DUP, OP_HASH160, # (as raw hex/bin data?)
redeemerAddr, OP_ELSE, redeemblocknum, OP_CHECKLOCKTIMEVERIFY, OP_DROP, OP_DUP, OP_HASH160, # can rm last op_equalverify (for direct pubkey comparison)
funderAddr, OP_ENDIF,OP_EQUALVERIFY, OP_CHECKSIG]) zec_redeemScript = CScript([
# print("Redeem script for p2sh contract on Zcash blockchain: ", b2x(zec_redeemScript)) 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() txin_scriptPubKey = zec_redeemScript.to_p2sh_scriptPubKey()
# Convert the P2SH scriptPubKey to a base58 Bitcoin address # Convert the P2SH scriptPubKey to a base58 Bitcoin address
txin_p2sh_address = CBitcoinAddress.from_scriptPubKey(txin_scriptPubKey) txin_p2sh_address = CBitcoinAddress.from_scriptPubKey(
txin_scriptPubKey)
p2sh = str(txin_p2sh_address) p2sh = str(txin_p2sh_address)
print("p2sh computed: ", p2sh) print("p2sh computed: ", p2sh)
# Import address as soon as you create it # Import address as soon as you create it
self.zcashd.importaddress(p2sh, "", False) self.zcashd.importaddress(p2sh, "", False)
# Returning all this to be saved locally in p2sh.json # 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} return {'p2sh': p2sh,
'redeemblocknum': redeemblocknum,
'redeemScript': b2x(zec_redeemScript),
'redeemer': redeemer,
'funder': funder,
'locktime': locktime}
def fund_htlc(self, p2sh, amount): def fund_htlc(self, p2sh, amount):
send_amount = float(amount)*COIN send_amount = float(amount) * COIN
# Import addr at same time as you fund # Import addr at same time as you fund
self.zcashd.importaddress(p2sh, "", False) self.zcashd.importaddress(p2sh, "", False)
fund_txid = self.zcashd.sendtoaddress(p2sh, send_amount) fund_txid = self.zcashd.sendtoaddress(p2sh, send_amount)
@ -75,13 +97,13 @@ class zcashProxy():
self.zcashd.importaddress(p2sh, "", False) self.zcashd.importaddress(p2sh, "", False)
# Get amount in address # Get amount in address
amount = self.zcashd.getreceivedbyaddress(p2sh, 0) amount = self.zcashd.getreceivedbyaddress(p2sh, 0)
amount = amount/COIN amount = amount / COIN
return amount return amount
def get_fund_status(self, p2sh): def get_fund_status(self, p2sh):
self.zcashd.importaddress(p2sh, "", False) self.zcashd.importaddress(p2sh, "", False)
amount = self.zcashd.getreceivedbyaddress(p2sh, 0) amount = self.zcashd.getreceivedbyaddress(p2sh, 0)
amount = amount/COIN amount = amount / COIN
print("Amount in zcash p2sh: ", amount, p2sh) print("Amount in zcash p2sh: ", amount, p2sh)
if amount > 0: if amount > 0:
return 'funded' return 'funded'
@ -108,7 +130,7 @@ class zcashProxy():
# print("TXINFO", decoded['vin'][0]) # print("TXINFO", decoded['vin'][0])
if('txid' in decoded['vin'][0]): if('txid' in decoded['vin'][0]):
sendid = decoded['vin'][0]['txid'] sendid = decoded['vin'][0]['txid']
if (sendid == fundtx_input ): if (sendid == fundtx_input):
print("Found funding tx: ", sendid) print("Found funding tx: ", sendid)
return self.parse_secret(lx(tx['txid'])) return self.parse_secret(lx(tx['txid']))
print("Redeem transaction with secret not found") print("Redeem transaction with secret not found")
@ -155,7 +177,7 @@ class zcashProxy():
def redeem_contract(self, contract, secret): def redeem_contract(self, contract, secret):
# How to find redeemScript and redeemblocknum from blockchain? # How to find redeemScript and redeemblocknum from blockchain?
p2sh = contract.p2sh p2sh = contract.p2sh
#checking there are funds in the address # checking there are funds in the address
amount = self.check_funds(p2sh) amount = self.check_funds(p2sh)
if(amount == 0): if(amount == 0):
print("Address ", p2sh, " not funded") print("Address ", p2sh, " not funded")
@ -184,7 +206,8 @@ class zcashProxy():
print('redeemPubKey', redeemPubKey) print('redeemPubKey', redeemPubKey)
zec_redeemScript = CScript(x(contract.redeemScript)) zec_redeemScript = CScript(x(contract.redeemScript))
txin = CMutableTxIn(fundtx['outpoint']) txin = CMutableTxIn(fundtx['outpoint'])
txout = CMutableTxOut(fundtx['amount'] - FEE, redeemPubKey.to_scriptPubKey()) txout = CMutableTxOut(fundtx['amount'] - FEE,
redeemPubKey.to_scriptPubKey())
# Create the unsigned raw transaction. # Create the unsigned raw transaction.
tx = CMutableTransaction([txin], [txout]) tx = CMutableTransaction([txin], [txout])
sighash = SignatureHash(zec_redeemScript, tx, 0, SIGHASH_ALL) sighash = SignatureHash(zec_redeemScript, tx, 0, SIGHASH_ALL)
@ -193,15 +216,17 @@ class zcashProxy():
sig = privkey.sign(sighash) + bytes([SIGHASH_ALL]) sig = privkey.sign(sighash) + bytes([SIGHASH_ALL])
print("SECRET", secret) print("SECRET", secret)
preimage = secret.encode('utf-8') preimage = secret.encode('utf-8')
txin.scriptSig = CScript([sig, privkey.pub, preimage, OP_TRUE, zec_redeemScript]) txin.scriptSig = CScript([sig, privkey.pub, preimage,
OP_TRUE, zec_redeemScript])
txin_scriptPubKey = zec_redeemScript.to_p2sh_scriptPubKey() txin_scriptPubKey = zec_redeemScript.to_p2sh_scriptPubKey()
print('Raw redeem transaction hex: ', b2x(tx.serialize())) print('Raw redeem transaction hex: ', b2x(tx.serialize()))
VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,)) VerifyScript(txin.scriptSig, txin_scriptPubKey,
tx, 0, (SCRIPT_VERIFY_P2SH,))
print("Script verified, sending raw redeem transaction...") print("Script verified, sending raw redeem transaction...")
txid = self.zcashd.sendrawtransaction(tx) txid = self.zcashd.sendrawtransaction(tx)
redeem_tx = b2x(lx(b2x(txid))) redeem_tx = b2x(lx(b2x(txid)))
fund_tx = str(fundtx['outpoint']) fund_tx = str(fundtx['outpoint'])
return {"redeem_tx": redeem_tx, "fund_tx": fund_tx} return {"redeem_tx": redeem_tx, "fund_tx": fund_tx}
def refund(self, contract): def refund(self, contract):
fundtx = self.find_transaction_to_address(contract.p2sh) fundtx = self.find_transaction_to_address(contract.p2sh)
@ -211,7 +236,9 @@ class zcashProxy():
redeemScript = CScript(x(contract.redeemScript)) redeemScript = CScript(x(contract.redeemScript))
txin = CMutableTxIn(fundtx['outpoint']) txin = CMutableTxIn(fundtx['outpoint'])
txout = CMutableTxOut(fundtx['amount'] - FEE, refundPubKey.to_scriptPubKey()) txout = CMutableTxOut(fundtx['amount'] - FEE,
refundPubKey.to_scriptPubKey())
# Create the unsigned raw transaction.
tx = CMutableTransaction([txin], [txout]) tx = CMutableTransaction([txin], [txout])
# Set nSequence and nLockTime # Set nSequence and nLockTime
txin.nSequence = 0 txin.nSequence = 0
@ -224,10 +251,11 @@ class zcashProxy():
txin.scriptSig = CScript([sig, privkey.pub, OP_FALSE, redeemScript]) txin.scriptSig = CScript([sig, privkey.pub, OP_FALSE, redeemScript])
txin_scriptPubKey = redeemScript.to_p2sh_scriptPubKey() txin_scriptPubKey = redeemScript.to_p2sh_scriptPubKey()
print('Raw redeem transaction hex: {0}'.format(b2x(tx.serialize()))) print('Raw redeem transaction hex: {0}'.format(b2x(tx.serialize())))
res = VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,)) res = VerifyScript(txin.scriptSig, txin_scriptPubKey,
tx, 0, (SCRIPT_VERIFY_P2SH,))
print("Script verified, sending raw transaction... (NOT)", res) print("Script verified, sending raw transaction... (NOT)", res)
txid = self.zcashd.sendrawtransaction(tx) txid = self.zcashd.sendrawtransaction(tx)
refund_tx = b2x(lx(b2x(txid))) refund_tx = b2x(lx(b2x(txid)))
fund_tx = str(fundtx['outpoint']) fund_tx = str(fundtx['outpoint'])
return {"refund_tx": refund_tx, "fund_tx": fund_tx} return {"refund_tx": refund_tx, "fund_tx": fund_tx}