diff --git a/apiForETHXCAT.py b/apiForETHXCAT.py new file mode 100644 index 0000000..88ac80d --- /dev/null +++ b/apiForETHXCAT.py @@ -0,0 +1,64 @@ +import zXcat +import bXcat +from xcat import * + +print("Starting test of xcat...") + +def Zcash_getaddr() + return zXcatForEth.zcashd.getnewaddresss() + + +def Zcash_fund(p2sh,amount) + fund_txid = zXcatForEth.zcashd.sendtoaddress(p2sh,amount) + return fund_txid + +def Zcash_getredeemscript_andp2sh(seller, buyer, hash_of_secret, lock_increment) + return zXcatForEth.hashtimelockcontract(seller, buyer, hash_of_secret, lock_increment) + +# finds seller's redeem tx and gets secret from it +def Zcash_get_secret(p2sh,fund_txid) + return zXcatForEth.find_secret(p2sh,fund_tx) + +def Zcash_refund(redeemscript,buyer_ad,p2sh,fund_txid) + return zXcatForEth. + +def Zcash_redeem(redeemscript,secret,p2sh, amount): + + txid = zXcatForETH.redeem_with_secret(trade.buyContract, secret, trade.sellContract) + return txid + +def redeem_buyer(): + print("BUYER REDEEMING SELL CONTRACT") + print("=============================") + trade = get_trade() + buyContract = trade.buyContract + sellContract = trade.sellContract + secret = "" + # if sellContract.get_status() == 'redeemed': + # raise RuntimeError("Sell contract was redeemed before buyer could retrieve funds") + # elif buyContract.get_status() == 'refunded': + # print("buyContract was refunded to buyer") + # else: + # Buy contract is where seller disclosed secret in redeeming + if buyContract.currency == 'bitcoin': + if (bXcat.still_locked(buyContract)): + if(not hasattr(buyContract,'fund_tx')): + print("Seems address has not been funded yet. Aborting.") + quit() + secret = bXcat.find_secret(buyContract.p2sh,buyContract.fund_tx) + if(secret != ""): + print("Found secret in seller's redeem tx on bitcoin chain:", secret) + else: + if zXcat.still_locked(buyContract): + secret = zXcat.find_secret(buyContract.p2sh,buyContract.fund_tx) + if(secret != ""): + print("Found secret in seller's redeem tx on zcash chain:", secret) + redeem_tx = redeem_p2sh(sellContract, secret, buyContract) + setattr(trade.sellContract, 'redeem_tx', redeem_tx) + save(trade) + + +def generate_blocks(num): + bXcat.generate(num) + zXcat.generate(num) + diff --git a/bXcat.py b/bXcat.py index f5e61ef..193fcd4 100644 --- a/bXcat.py +++ b/bXcat.py @@ -283,7 +283,7 @@ def find_secret(p2sh,vinid): bitcoind.importaddress(p2sh, "", True) # is this working? - txs = bitcoind.listtransactions() + txs = bitcoind.listreceivedbyaddress() # print("==========================================LISTTT============", txs) # print() for tx in txs: diff --git a/secret.json b/secret.json index 0fc274e..e508d60 100644 --- a/secret.json +++ b/secret.json @@ -1 +1 @@ -N9BkxaG0 \ No newline at end of file +4tqcS72n \ No newline at end of file diff --git a/test.py b/test.py index 2556b44..c7bc530 100644 --- a/test.py +++ b/test.py @@ -6,10 +6,10 @@ htlcTrade = initiate() fund_buyer() # zXcat.generate(8) -zXcat.generate(6) +# zXcat.generate(6) redeem_seller() -zXcat.generate(2) -bXcat.generate(20) +zXcat.generate(1) +bXcat.generate(1) redeem_buyer() @@ -17,3 +17,4 @@ redeem_buyer() # print(addr) # # print(b2x('tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ')) # print(b2x(addr)) +e \ No newline at end of file diff --git a/xcat.json b/xcat.json index efa3cdf..46ea62d 100644 --- a/xcat.json +++ b/xcat.json @@ -1 +1 @@ -{"sell": {"currency": "zcash", "initiator": "tmYU7UVFGVK2cmNqQ9dLoNFZMWSvZWCeKtD", "redeemblocknum": 2109, "amount": "0.01", "fulfiller": "tmVx4ckwZAZFPwxMGgrpZ6N7wp3VRgDuXfy", "fund_tx": "ebaa6f3c99ed282701eb8ed926ca822cd9b52458f72e9170988920ae1cffd7ff", "redeem_tx": false, "p2sh": "t2VK8Pgu4GEfnBrh8bLTND1ns3bfQUw7wP9", "redeemScript": "63a8202bf467e67c060bc84c7cc1a47791d8678d8eac11a9e80af993b767661131e7f38876a914ddfa63110b8569bc80c0e5cbe3ea9df142ddea6e67023d08b17576a914f99980ad0a78f8f742d88d97726cc9c91df172f86888ac"}, "buy": {"currency": "bitcoin", "initiator": "mrjxeVKPWCEQYEU9du3CnGT9NKpVNUzSKz", "redeemblocknum": 7936, "amount": "1.12", "fulfiller": "miN9ygMUHEipaobPFuoRwmsNN3ovwzPMDS", "fund_tx": "c7dd7447cbc8717d4574011bcf75c727425ac0585f2aac7f3a87365bbbfedc09", "redeem_tx": "31f691e284f9ea59eb76d691a9442f8dda4897c69772fa4b986f3e4eb1d63b90", "p2sh": "2MtDXWhoi4PwA66pCbusQpGTdGeN6UYTQEu", "redeemScript": "63a8202bf467e67c060bc84c7cc1a47791d8678d8eac11a9e80af993b767661131e7f38876a9147b1f15d1da93ba0414fe7ac36297a2bb7749a3ef6702001fb17576a9141f3e62e6daa403faea8bc8a30259f0888a1a7cb06888ac"}} \ No newline at end of file +{"sell": {"fulfiller": "tmP3jJoyhBzAxeqpTYW9HrAt1XvJFg9gttc", "amount": "0.01", "redeemScript": "63a82044ddae444a583f19d0ebdd539cc7abb7e1aa4e54fac042486a5c1b935c4a0bf88876a9149243e230812261371e6743055ee8c6ea4181f80567024608b17576a9148e4144cee39078239a6dcd026ac65043a76254486888ac", "initiator": "tmNgXUNBTfH9TD4TwgEKK8K6Aytjph3QGtZ", "fund_tx": "9bb403ad881781818ea193f2c23eeafd02190b9aac8944b22650ffc6faff2736", "redeemblocknum": 2118, "currency": "zcash", "p2sh": "t2NPhNuYvwXVpiFA3QiZjgaeSDZwQVVKmUT"}, "buy": {"fulfiller": "mj12n62KmrWLCsjrpG1PN8iQnLy4J518s7", "amount": "1.12", "redeemScript": "63a82044ddae444a583f19d0ebdd539cc7abb7e1aa4e54fac042486a5c1b935c4a0bf88876a9141991d55a642946f5a8def2d69116619f659b89626702151fb17576a9142637c44bba94afbe236eb7587cc67e0682cd3d186888ac", "initiator": "mhr9tXrXmCXipK1ogWkhbESAmaYN1DfCQG", "fund_tx": "9c3bc5dc1aca5e722aa43f632fd7b130d9c5f972cad0f75236a210e80b1a466f", "redeemblocknum": 7957, "redeem_tx": "18ae4ff1a30111154ea40f265a47e5f18fc8b2cb785c9de80f71ad4b577d2f46", "currency": "bitcoin", "p2sh": "2N8Ru77EQ1qZtzBieZ17Q8wdEZA9SjiTJmj"}} \ No newline at end of file diff --git a/zXcatForEth.py b/zXcatForEth.py new file mode 100644 index 0000000..670e504 --- /dev/null +++ b/zXcatForEth.py @@ -0,0 +1,360 @@ +#!/usr/bin/env python3 + +# Based on spend-p2sh-txout.py from python-bitcoinlib. +# Copyright (C) 2017 The Zcash developers + +# small modifications from zXcat.py to be convenient for ETH xcat + +import sys +if sys.version_info.major < 3: + sys.stderr.write('Sorry, Python 3.x required by this example.\n') + sys.exit(1) + +import zcash +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 bitcoin +from utils import * + +# SelectParams('testnet') +SelectParams('regtest') +zcashd = zcash.rpc.Proxy() +FEE = 0.001*COIN + + +def get_keys(funder_address, redeemer_address): + fundpubkey = CBitcoinAddress(funder_address) + redeempubkey = CBitcoinAddress(redeemer_address) + # fundpubkey = zcashd.getnewaddress() + # redeempubkey = zcashd.getnewaddress() + return fundpubkey, redeempubkey + +def privkey(address): + zcashd.dumpprivkey(address) + +def hashtimelockcontract(funder, redeemer, hash_of_secret, lock_increment): + funderAddr = CBitcoinAddress(funder) + redeemerAddr = CBitcoinAddress(redeemer) + blocknum = zcashd.getblockcount() + print("Current blocknum", blocknum) + redeemblocknum = blocknum + lock_increment + print("REDEEMBLOCKNUM ZCASH", redeemblocknum) + zec_redeemScript = CScript([OP_IF, OP_SHA256, hash_of_secret, OP_EQUALVERIFY,OP_DUP, OP_HASH160, + redeemerAddr, OP_ELSE, redeemblocknum, OP_CHECKLOCKTIMEVERIFY, OP_DROP, OP_DUP, OP_HASH160, + funderAddr, OP_ENDIF,OP_EQUALVERIFY, OP_CHECKSIG]) + print("Redeem script for p2sh contract on Zcash blockchain:", b2x(zec_redeemScript)) + txin_scriptPubKey = zec_redeemScript.to_p2sh_scriptPubKey() + # Convert the P2SH scriptPubKey to a base58 Bitcoin address + txin_p2sh_address = CBitcoinAddress.from_scriptPubKey(txin_scriptPubKey) + p2sh = str(txin_p2sh_address) + # Returning all this to be saved locally in p2sh.json + return { 'redeemScript': b2x(zec_redeemScript), 'p2sh': p2sh} + + + +def fund_htlc(p2sh, amount): + send_amount = float(amount)*COIN + fund_txid = zcashd.sendtoaddress(p2sh, send_amount) + txid = b2x(lx(b2x(fund_txid))) + return txid + +def check_funds(p2sh): + zcashd.importaddress(p2sh, "", False) #Ariel: changed this to true + print("Imported address", p2sh) + # Get amount in address + amount = zcashd.getreceivedbyaddress(p2sh, 0) + print("Amount in address", amount) + amount = amount/COIN + return amount + +def get_tx_details(txid): + fund_txinfo = zcashd.gettransaction(txid) + return fund_txinfo['details'][0] + +def find_transaction_to_address(p2sh): + zcashd.importaddress(p2sh, "", False) + txs = zcashd.listunspent() + for tx in txs: + if tx['address'] == CBitcoinAddress(p2sh): + print("Found tx to p2sh", p2sh) + return tx + +# def get_tx_details(txid): +# # This method is problematic I haven't gotten the type conversions right +# print(bytearray.fromhex(txid)) +# print(b2x(bytearray.fromhex(txid))) +# fund_txinfo = zcashd.gettransaction(bytearray.fromhex(txid)) +# print(fund_txinfo) +# +# return fund_txinfo['details'][0] +def find_secret(p2sh,vinid): + zcashd.importaddress(p2sh, "", True) + # is this working? + + txs = zcashd.listtransactions() + # print("==========================================LISTTT============", txs) + # print() + # print('LENNNNNNN:', len(txs)) + # print('LENNNNNNN2:', len(txs)) + for tx in txs: + # print("tx addr:", tx['address'], "tx id:", tx['txid']) + # print(type(tx['address'])) + # print(type(p2sh)) + # print('type::',type(tx['txid'])) + raw = zcashd.gettransaction(lx(tx['txid']))['hex'] + decoded = zcashd.decoderawtransaction(raw) + # print("fdsfdfds", decoded['vin'][0]) + if('txid' in decoded['vin'][0]): + sendid = decoded['vin'][0]['txid'] + # print("sendid:", sendid) + + if (sendid == vinid ): + # print(type(tx['txid'])) + # print(str.encode(tx['txid'])) + return parse_secret(lx(tx['txid'])) + print("Redeem transaction with secret not found") + return "" + + +def parse_secret(txid): + raw = zcashd.gettransaction(txid)['hex'] + # print("Raw", raw) + decoded = zcashd.decoderawtransaction(raw) + scriptSig = decoded['vin'][0]['scriptSig'] + print("Decoded", scriptSig) + asm = scriptSig['asm'].split(" ") + pubkey = asm[1] + secret = hex2str(asm[2]) + redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey)) + print('redeemPubkey', redeemPubkey) + print(secret) + return secret + +# redeems automatically after buyer has funded tx, by scanning for transaction to the p2sh +# i.e., doesn't require buyer telling us fund txid + +def auto_redeem(p2sh, secret): +# How to find redeemScript and redeemblocknum from blockchain? + print("Contract in auto redeem", contract.__dict__) + p2sh = contract.p2sh + #checking there are funds in the address + amount = check_funds(p2sh) + if(amount == 0): + print("address ", p2sh, " not funded") + quit() + fundtx = find_transaction_to_address(p2sh) + amount = fundtx['amount'] / COIN + p2sh = P2SHBitcoinAddress(p2sh) + if fundtx['address'] == p2sh: + print("Found {0} in p2sh {1}, redeeming...".format(amount, p2sh)) + + # Parsing redeemblocknum from the redeemscript of the p2sh + redeemblocknum = find_redeemblocknum(contract) + blockcount = zcashd.getblockcount() + print("\nCurrent blocknum at time of redeem on Zcash chain:", blockcount) + if blockcount < redeemblocknum: + redeemPubKey = find_redeemAddr(contract) + print('redeemPubKey', redeemPubKey) + else: + print("nLocktime exceeded, refunding") + redeemPubKey = find_refundAddr(contract) + print('refundPubKey', redeemPubKey) + # redeemPubKey = CBitcoinAddress.from_scriptPubKey(redeemPubKey) + # exit() + + zec_redeemScript = CScript(x(contract.redeemScript)) + txin = CMutableTxIn(fundtx['outpoint']) + txout = CMutableTxOut(fundtx['amount'] - FEE, redeemPubKey.to_scriptPubKey()) + # Create the unsigned raw transaction. + tx = CMutableTransaction([txin], [txout]) + # nLockTime needs to be at least as large as parameter of CHECKLOCKTIMEVERIFY for script to verify + # TODO: these things like redeemblocknum should really be properties of a tx class... + # Need: redeemblocknum, zec_redeemScript, secret (for creator...), txid, redeemer... + if blockcount >= redeemblocknum: + print("\nLocktime exceeded") + tx.nLockTime = redeemblocknum + sighash = SignatureHash(zec_redeemScript, tx, 0, SIGHASH_ALL) + # TODO: figure out how to better protect privkey + privkey = zcashd.dumpprivkey(redeemPubKey) + sig = privkey.sign(sighash) + bytes([SIGHASH_ALL]) + print("SECRET", secret) + preimage = secret.encode('utf-8') + txin.scriptSig = CScript([sig, privkey.pub, preimage, OP_TRUE, zec_redeemScript]) + + # exit() + + # print("txin.scriptSig", b2x(txin.scriptSig)) + txin_scriptPubKey = zec_redeemScript.to_p2sh_scriptPubKey() + # print('Redeem txhex', b2x(tx.serialize())) + VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,)) + print("script verified, sending raw tx") + txid = bitcoind.sendrawtransaction(tx) + print("Txid of submitted redeem tx: ", b2x(lx(b2x(txid)))) + return b2x(lx(b2x(txid))) + else: + print("No contract for this p2sh found in database", p2sh) + +def parse_script(script_hex): + redeemScript = zcashd.decodescript(script_hex) + scriptarray = redeemScript['asm'].split(' ') + return scriptarray + +def find_redeemblocknum(redeemscript): + scriptarray = parse_script(redeemScript) + redeemblocknum = scriptarray[8] + return int(redeemblocknum) + +def find_redeemAddr(redeemscript): + scriptarray = parse_script(redeemScript) + redeemer = scriptarray[6] + redeemAddr = P2PKHBitcoinAddress.from_bytes(x(redeemer)) + return redeemAddr + +def find_refundAddr(redeemscript): + scriptarray = parse_script(redeemScript) + funder = scriptarray[13] + refundAddr = P2PKHBitcoinAddress.from_bytes(x(funder)) + return refundAddr + +def find_recipient(contract): + # make this dependent on actual fund tx to p2sh, not contract + txid = contract.fund_tx + raw = zcashd.gettransaction(lx(txid), True)['hex'] + # print("Raw", raw) + decoded = zcashd.decoderawtransaction(raw) + scriptSig = decoded['vin'][0]['scriptSig'] + print("Decoded", scriptSig) + asm = scriptSig['asm'].split(" ") + pubkey = asm[1] + initiator = CBitcoinAddress(contract.initiator) + fulfiller = CBitcoinAddress(contract.fulfiller) + print("Initiator", b2x(initiator)) + print("Fulfiler", b2x(fulfiller)) + print('pubkey', pubkey) + redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey)) + print('redeemPubkey', redeemPubkey) + +# addr = CBitcoinAddress('tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ') +# print(addr) +# # print(b2x('tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ')) +# print(b2x(addr)) + +def new_zcash_addr(): + addr = zcashd.getnewaddress() + print('new ZEC addr', addr) + return addr + +def generate(num): + blocks = zcashd.generate(num) + return blocks + + + + +# redeems funded tx automatically, by scanning for transaction to the p2sh +# i.e., doesn't require buyer telling us fund txid +# returns false if fund tx doesn't exist or is too small +# minamout - the minimal amount your're expecting +# assumes your Zcash client has the private key of the legit redeemer +def redeem_with_secret(redeemscript,secret,p2sh,minamount): + # How to find redeemScript and redeemblocknum from blockchain? + # print("Redeeming contract using secret", contract.__dict__) + #checking there are funds in the address + amount = check_funds(p2sh) + if(amount < minamount): + print("address ", p2sh, " not sufficiently funded") + return false + fundtx = find_transaction_to_address(p2sh) + amount = fundtx['amount'] / COIN + p2sh = P2SHBitcoinAddress(p2sh) + if fundtx['address'] == p2sh: + print("Found {0} in p2sh {1}, redeeming...".format(amount, p2sh)) + + redeemPubKey = find_redeemAddr(redeemscript) + print('redeemPubKey', redeemPubKey) + + redeemScriptObject = CScript(x(redeemScript)) + txin = CMutableTxIn(fundtx['outpoint']) + txout = CMutableTxOut(fundtx['amount'] - FEE, redeemPubKey.to_scriptPubKey()) + # Create the unsigned raw transaction. + tx = CMutableTransaction([txin], [txout]) + sighash = SignatureHash(redeemScriptObject, tx, 0, SIGHASH_ALL) + # TODO: figure out how to better protect privkey + privkey = zcashd.dumpprivkey(redeemPubKey) + sig = privkey.sign(sighash) + bytes([SIGHASH_ALL]) + print("SECRET", secret) + preimage = secret.encode('utf-8') + txin.scriptSig = CScript([sig, privkey.pub, preimage, OP_TRUE, redeemScriptObject]) + + # exit() + + # print("txin.scriptSig", b2x(txin.scriptSig)) + txin_scriptPubKey = redeemScriptObject.to_p2sh_scriptPubKey() + # print('Redeem txhex', b2x(tx.serialize())) + VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,)) + print("script verified, sending raw tx") + txid = zcashd.sendrawtransaction(tx) + print("Txid of submitted redeem tx: ", b2x(lx(b2x(txid)))) + return b2x(lx(b2x(txid))) + else: + print("No contract for this p2sh found in database", p2sh) + + + +# given a contract return true or false according to whether the relevant fund tx's timelock is still valid +def still_locked(contract): + p2sh = contract.p2sh + # Parsing redeemblocknum from the redeemscript of the p2sh + redeemblocknum = find_redeemblocknum(contract) + blockcount = zcashd.getblockcount() + return (int(blockcount) < int(redeemblocknum)) + +def redeem_after_timelock(redeemscript,redeempubkey,p2sh,fund_txid): + amount = fundtx['amount'] / COIN + + if (fundtx['address'].__str__() != p2sh): + print("no fund transaction found to the contract p2sh address ",p2sh) + quit() + # print("Found fundtx:", fundtx) + # Parsing redeemblocknum from the redeemscript of the p2sh + redeemblocknum = find_redeemblocknum(redeemscript) + blockcount = zcashd.getblockcount() + print ("Current block:", blockcount, "Can redeem from block:", redeemblocknum) + if(still_locked(contract)): + print("too early for redeeming with timelock try again at block", redeemblocknum, "or later") + return + + print("Found {0} in p2sh {1}, redeeming...".format(amount, p2sh)) + + + redeemPubKey = find_refundAddr(redeemscript) + print('refundPubKey', redeemPubKey) + + redeemScriptObject = CScript(x(contract.redeemScript)) + txin = CMutableTxIn(fundtx['outpoint']) + txout = CMutableTxOut(fundtx['amount'] - FEE, redeemPubKey.to_scriptPubKey()) + # Create the unsigned raw transaction. + txin.nSequence = 0 + tx = CMutableTransaction([txin], [txout]) + tx.nLockTime = redeemblocknum + + sighash = SignatureHash(redeemScriptObject, tx, 0, SIGHASH_ALL) + # TODO: figure out how to better protect privkey + privkey = zcashd.dumpprivkey(redeemPubKey) + sig = privkey.sign(sighash) + bytes([SIGHASH_ALL]) + txin.scriptSig = CScript([sig, privkey.pub, OP_FALSE, redeemScriptObject]) + + # exit() + + # print("txin.scriptSig", b2x(txin.scriptSig)) + txin_scriptPubKey = redeemScriptObject.to_p2sh_scriptPubKey() + # print('Redeem txhex', b2x(tx.serialize())) + VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,)) + print("script verified, sending raw tx") + txid = zcashd.sendrawtransaction(tx) + print("Txid of submitted redeem tx: ", b2x(lx(b2x(txid)))) + return b2x(lx(b2x(txid)))