diff --git a/.gitignore b/.gitignore index 0ddc043..a06fbbd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.pyc xcatdb/ +xcat.egg-info/ diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 8d6267e..0000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -python-bitcoinlib -plyvel diff --git a/secret.json b/secret.json deleted file mode 100644 index 8503f44..0000000 --- a/secret.json +++ /dev/null @@ -1 +0,0 @@ -2E8ASX0w \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..8404c0e --- /dev/null +++ b/setup.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +from setuptools import setup, find_packages +from xcat import version + +setup( + name="xcat", + version=version, + entry_points = { + "console_scripts": ['xcat = xcat.cli:main'] + }, + description="Xcat is a package to facilitate cross-chain atomic transactions.", + author="arcalinea and arielgabizon", + author_email="xcat@z.cash", + license="MIT", + url="http://github.com/zcash/xcat", + packages=find_packages() +) diff --git a/xcat-runner.py b/xcat-runner.py new file mode 100644 index 0000000..4353384 --- /dev/null +++ b/xcat-runner.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from xcat.xcat import main + +if __name__ == '__main__': + main() diff --git a/xcat/__init__.py b/xcat/__init__.py new file mode 100644 index 0000000..89503f4 --- /dev/null +++ b/xcat/__init__.py @@ -0,0 +1,5 @@ +""" +Xcat is an implementation of a cross-chain atomic transaction. The protocol currently support trades between Zcash and Bitcoin. +""" +version_info = (0, 1) +version = '.'.join(map(str, version_info)) diff --git a/xcat/__main__.py b/xcat/__main__.py new file mode 100644 index 0000000..130bc63 --- /dev/null +++ b/xcat/__main__.py @@ -0,0 +1,2 @@ +from .cli import main +main() diff --git a/bXcat.py b/xcat/bitcoinRPC.py similarity index 99% rename from bXcat.py rename to xcat/bitcoinRPC.py index 7005c78..d5506d6 100644 --- a/bXcat.py +++ b/xcat/bitcoinRPC.py @@ -14,13 +14,13 @@ from bitcoin.core.script import CScript, OP_DUP, OP_IF, OP_ELSE, OP_ENDIF, OP_HA from bitcoin.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH from bitcoin.wallet import CBitcoinAddress, CBitcoinSecret, P2SHBitcoinAddress, P2PKHBitcoinAddress -from utils import * +from xcat.utils import * import zcash import zcash.rpc import pprint, json -from zXcat import parse_script +from xcat.zcashRPC import parse_script # SelectParams('testnet') SelectParams('regtest') diff --git a/cli.py b/xcat/cli.py similarity index 75% rename from cli.py rename to xcat/cli.py index 0abcd3a..bd07110 100644 --- a/cli.py +++ b/xcat/cli.py @@ -1,14 +1,16 @@ import argparse, textwrap -from utils import * -import database as db -import bXcat, zXcat -from trades import * -from xcat import * +from xcat.utils import * +import xcat.database as db +import xcat.bitcoinRPC +import xcat.zcashRPC +import xcat.userInput +from xcat.trades import * +from xcat.protocol import * import ast -def save_state(trade): +def save_state(trade, tradeid): save(trade) - db.create + db.create(trade, tradeid) def checkSellStatus(trade): if trade.buy.get_status() == 'funded': @@ -49,38 +51,58 @@ def checkBuyStatus(trade): print("TXID after buyer redeem", txid) print("XCAT trade complete!") +# Import a trade in hex, and save to db +def importtrade(hexstr): + trade = x2s(hexstr) + trade = instantiateTrade(ast.literal_eval(trade)) + save_state(trade) + +# Export a trade by its tradeid +def exporttrade(tradeid): + # trade = get_trade() + trade = db.get(tradeid) + hexstr = s2x(str(trade)) + print(trade) + print(hexstr) + +def newtrade(tradeid): + erase_trade() + role = 'seller' + print("Creating new XCAT trade...") + trade = seller_init(Trade()) + # Save it to leveldb + # db.create(trade) + save_state(trade, tradeid) + def instantiateTrade(trade): return Trade(buy=Contract(trade['buy']), sell=Contract(trade['sell'])) -if __name__ == '__main__': +def main(): parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, description=textwrap.dedent('''\ == Trades == newtrade - create a new trade checktrades - check for actions to be taken on existing trades importtrade "hexstr" - import an existing trade from a hex string - exporttrade - export the data of an existing xcat trade as a hex string + exporttrade - export the data of an existing trade as a hex string. Takes the tradeid as an argument findtrade - find a trade by the txid of the currency being traded out of ''')) parser.add_argument("command", action="store", help="list commands") parser.add_argument("argument", action="store", nargs="*", help="add an argument") # parser.add_argument("--daemon", "-d", action="store_true", help="Run as daemon process") + # TODO: function to view available trades + # TODO: function to tell if tradeid already exists for newtrade args = parser.parse_args() # how to hold state of role command = args.command if command == 'importtrade': hexstr = args.argument[0] - trade = x2s(hexstr) - trade = instantiateTrade(ast.literal_eval(trade)) - save_state(trade) - # print(trade.toJ) + importtrade(hexstr) elif command == 'exporttrade': - trade = get_trade() - hexstr = s2x(str(trade)) - print(trade) - print(hexstr) + tradeid = args.argument[0] + exporttrade(tradeid) elif command == 'checktrades': trade = get_trade() trade = instantiateTrade(trade) @@ -91,12 +113,12 @@ if __name__ == '__main__': role = 'buyer' checkBuyStatus(trade) elif command == 'newtrade': - erase_trade() - role = 'seller' - print("Creating new XCAT trade...") - trade = seller_init(Trade()) - # Save it to leveldb - db.create(trade) + try: + tradeid = args.argument[0] + newtrade(tradeid) + except: + tradeid = userInput.enter_trade_id() + newtrade(tradeid) elif command == "daemon": #TODO: implement print("Run as daemon process") diff --git a/database.py b/xcat/database.py similarity index 60% rename from database.py rename to xcat/database.py index 39b4422..1d00ed8 100644 --- a/database.py +++ b/xcat/database.py @@ -1,29 +1,35 @@ import plyvel -from utils import * +from .utils import * import binascii import sys import json db = plyvel.DB('/tmp/testdb', create_if_missing=True) -trade = get_trade() -## txid we retrieve by -if trade and trade.sell: - if hasattr(trade.sell, 'fund_tx'): - txid = trade.sell.fund_tx +# trade = get_trade() +# ## txid we retrieve by +# if trade and trade.sell: +# if hasattr(trade.sell, 'fund_tx'): +# txid = trade.sell.fund_tx # Takes object, saves json as bytes -def create(trade): +def create(trade, tradeid): trade = trade.toJSON() + db.put(b(tradeid), b(trade)) + +# Uses the funding txid as the key to save trade +def createByFundtx(trade): + trade = trade.toJSON() + # # Save trade by initiating txid jt = json.loads(trade) txid = jt['sell']['fund_tx'] - # Save trade by initiating txid db.put(b(txid), b(trade)) def get(txid): return db.get(b(txid)) # db.delete(b'hello') +db.get(b'test') # hexstr = get(txid) # print(x2s(hexstr)) @@ -33,8 +39,9 @@ def print_entries(): 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']) + # print('sell: ', j['sell']) # print_entries() # txid = '1171aeda64eff388b3568fa4675d0ca78852911109bbe42e0ef11ad6bf1b159e' diff --git a/xcat.py b/xcat/protocol.py similarity index 86% rename from xcat.py rename to xcat/protocol.py index 7abf4f2..6f660db 100644 --- a/xcat.py +++ b/xcat/protocol.py @@ -1,35 +1,35 @@ -import zXcat -import bXcat -from utils import * from waiting import * from time import sleep import json import os, sys from pprint import pprint -from trades import Contract, Trade -import userInput +import xcat.zcashRPC +import xcat.bitcoinRPC +from xcat.utils import * +from xcat.trades import Contract, Trade +import xcat.userInput def check_p2sh(currency, address): if currency == 'bitcoin': print("Checking funds in Bitcoin p2sh") - return bXcat.check_funds(address) + return bitcoinRPC.check_funds(address) else: print("Checking funds in Zcash p2sh") - return zXcat.check_funds(address) + return zcashRPC.check_funds(address) def create_htlc(currency, funder, redeemer, commitment, locktime): print("Commitment in create_htlc", commitment) if currency == 'bitcoin': - sell_p2sh = bXcat.hashtimelockcontract(funder, redeemer, commitment, locktime) + sell_p2sh = bitcoinRPC.hashtimelockcontract(funder, redeemer, commitment, locktime) else: - sell_p2sh = zXcat.hashtimelockcontract(funder, redeemer, commitment, locktime) + sell_p2sh = zcashRPC.hashtimelockcontract(funder, redeemer, commitment, locktime) return sell_p2sh def fund_htlc(currency, p2sh, amount): if currency == 'bitcoin': - txid = bXcat.fund_htlc(p2sh, amount) + txid = bitcoinRPC.fund_htlc(p2sh, amount) else: - txid = zXcat.fund_htlc(p2sh, amount) + txid = zcashRPC.fund_htlc(p2sh, amount) return txid # # def fund_buy_contract(trade): @@ -80,25 +80,25 @@ def create_buy_p2sh(trade, commitment, locktime): def auto_redeem_p2sh(contract, secret): currency = contract.currency if currency == 'bitcoin': - res = bXcat.auto_redeem(contract, secret) + res = bitcoinRPC.auto_redeem(contract, secret) else: - res = zXcat.auto_redeem(contract, secret) + res = zcashRPC.auto_redeem(contract, secret) return res def redeem_p2sh(contract, secret): currency = contract.currency if currency == 'bitcoin': - res = bXcat.redeem_contract(contract, secret) + res = bitcoinRPC.redeem_contract(contract, secret) else: - res = zXcat.redeem_contract(contract, secret) + res = zcashRPC.redeem_contract(contract, secret) return res def parse_secret(chain, txid): if chain == 'bitcoin': - secret = bXcat.parse_secret(txid) + secret = bitcoinRPC.parse_secret(txid) else: - secret = zXcat.parse_secret(txid) + secret = zcashRPC.parse_secret(txid) #### Main functions determining user flow from command line def buyer_redeem(trade): @@ -112,9 +112,9 @@ def buyer_redeem(trade): currency = trade.sell.currency # Buy contract is where seller disclosed secret in redeeming if trade.buy.currency == 'bitcoin': - secret = bXcat.parse_secret(trade.buy.redeem_tx) + secret = bitcoinRPC.parse_secret(trade.buy.redeem_tx) else: - secret = zXcat.parse_secret(trade.buy.redeem_tx) + secret = zcashRPC.parse_secret(trade.buy.redeem_tx) print("Found secret in seller's redeem tx", secret) redeem_tx = redeem_p2sh(trade.sell, secret) setattr(trade.sell, 'redeem_tx', redeem_tx) diff --git a/xcat/secret.json b/xcat/secret.json new file mode 100644 index 0000000..c7049a5 --- /dev/null +++ b/xcat/secret.json @@ -0,0 +1 @@ +OBbU9kny \ No newline at end of file diff --git a/test.py b/xcat/tests/test.py similarity index 98% rename from test.py rename to xcat/tests/test.py index c3763d8..1cc40ca 100644 --- a/test.py +++ b/xcat/tests/test.py @@ -1,5 +1,5 @@ -import zXcat -import bXcat +import xcat.zcash +import xcat.bitcoin from xcat import * htlcTrade = Trade() diff --git a/xcat.json b/xcat/tests/test_cli.py similarity index 100% rename from xcat.json rename to xcat/tests/test_cli.py diff --git a/xcat/tests/test_db.py b/xcat/tests/test_db.py new file mode 100644 index 0000000..6b6285e --- /dev/null +++ b/xcat/tests/test_db.py @@ -0,0 +1,21 @@ +import xcat.database as db +import unittest, json +import xcat.trades as trades + +class DatabaseTest(unittest.TestCase): + def setUp(self): + self.data = {"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"} + self.sell = trades.Contract(self.data['sell']) + + def test_create(self): + sell = trades.Contract(self.data['sell']) + buy = trades.Contract(self.data['buy']) + trade = trades.Trade(sell, buy, commitment=self.data['commitment']) + db.create(trade, 'test') + + def test_get(self): + trade = db.get('test') + print("Trade") + +if __name__ == '__main__': + unittest.main() diff --git a/xcat/tests/tradetest.json b/xcat/tests/tradetest.json new file mode 100644 index 0000000..86b97a5 --- /dev/null +++ b/xcat/tests/tradetest.json @@ -0,0 +1 @@ +{"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"} diff --git a/trades.py b/xcat/trades.py similarity index 100% rename from trades.py rename to xcat/trades.py diff --git a/userInput.py b/xcat/userInput.py similarity index 96% rename from userInput.py rename to xcat/userInput.py index bd19a90..fdbd095 100644 --- a/userInput.py +++ b/xcat/userInput.py @@ -1,4 +1,8 @@ -from utils import * +from xcat.utils import * + +def enter_trade_id(): + tradeid = input("Enter a unique identifier for this trade: ") + return tradeid def get_trade_amounts(): amounts = {} diff --git a/utils.py b/xcat/utils.py similarity index 99% rename from utils.py rename to xcat/utils.py index 9ba24af..06e1d2c 100644 --- a/utils.py +++ b/xcat/utils.py @@ -1,5 +1,5 @@ import hashlib, json, random, binascii -import trades +import xcat.trades ############################################ ########### Data conversion utils ########## diff --git a/xcat/xcat.json b/xcat/xcat.json new file mode 100644 index 0000000..f4bc38a --- /dev/null +++ b/xcat/xcat.json @@ -0,0 +1 @@ +{"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"} \ No newline at end of file diff --git a/zXcat.py b/xcat/zcashRPC.py similarity index 99% rename from zXcat.py rename to xcat/zcashRPC.py index 16c5bfd..711a71d 100644 --- a/zXcat.py +++ b/xcat/zcashRPC.py @@ -16,7 +16,7 @@ from zcash.core.script import CScript, OP_DUP, OP_IF, OP_ELSE, OP_ENDIF, OP_HASH from zcash.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH from zcash.wallet import CBitcoinAddress, CBitcoinSecret, P2SHBitcoinAddress, P2PKHBitcoinAddress -from utils import * +from xcat.utils import * # SelectParams('testnet') SelectParams('regtest')