Rename to commitment
This commit is contained in:
parent
2b7d00b73e
commit
941ef40390
79
bXcat.py
79
bXcat.py
|
@ -47,15 +47,15 @@ def get_keys(funder_address, redeemer_address):
|
||||||
def privkey(address):
|
def privkey(address):
|
||||||
bitcoind.dumpprivkey(address)
|
bitcoind.dumpprivkey(address)
|
||||||
|
|
||||||
def hashtimelockcontract(funder, redeemer, secret, locktime):
|
def hashtimelockcontract(funder, redeemer, commitment, locktime):
|
||||||
funderAddr = CBitcoinAddress(funder)
|
funderAddr = CBitcoinAddress(funder)
|
||||||
redeemerAddr = CBitcoinAddress(redeemer)
|
redeemerAddr = CBitcoinAddress(redeemer)
|
||||||
h = sha256(secret)
|
# h = sha256(secret)
|
||||||
blocknum = bitcoind.getblockcount()
|
blocknum = bitcoind.getblockcount()
|
||||||
print("Current blocknum", blocknum)
|
print("Current blocknum", blocknum)
|
||||||
redeemblocknum = blocknum + locktime
|
redeemblocknum = blocknum + locktime
|
||||||
print("REDEEMBLOCKNUM BITCOIN", redeemblocknum)
|
print("REDEEMBLOCKNUM BITCOIN", redeemblocknum)
|
||||||
redeemScript = CScript([OP_IF, OP_SHA256, h, OP_EQUALVERIFY,OP_DUP, OP_HASH160,
|
redeemScript = CScript([OP_IF, OP_SHA256, x(commitment), OP_EQUALVERIFY,OP_DUP, OP_HASH160,
|
||||||
redeemerAddr, OP_ELSE, redeemblocknum, OP_CHECKLOCKTIMEVERIFY, OP_DROP, OP_DUP, OP_HASH160,
|
redeemerAddr, OP_ELSE, redeemblocknum, OP_CHECKLOCKTIMEVERIFY, OP_DROP, OP_DUP, OP_HASH160,
|
||||||
funderAddr, OP_ENDIF,OP_EQUALVERIFY, OP_CHECKSIG])
|
funderAddr, OP_ENDIF,OP_EQUALVERIFY, OP_CHECKSIG])
|
||||||
print("Redeem script for p2sh contract on Bitcoin blockchain:", b2x(redeemScript))
|
print("Redeem script for p2sh contract on Bitcoin blockchain:", b2x(redeemScript))
|
||||||
|
@ -108,6 +108,11 @@ def get_tx_details(txid):
|
||||||
# redeems automatically after buyer has funded tx, by scanning for transaction to the p2sh
|
# redeems automatically after buyer has funded tx, by scanning for transaction to the p2sh
|
||||||
# i.e., doesn't require buyer telling us fund txid
|
# i.e., doesn't require buyer telling us fund txid
|
||||||
def auto_redeem(contract, secret):
|
def auto_redeem(contract, secret):
|
||||||
|
print("Parsing script for auto_redeem...")
|
||||||
|
scriptarray = parse_script(contract.redeemScript)
|
||||||
|
redeemblocknum = scriptarray[8]
|
||||||
|
redeemPubkey = scriptarray[6]
|
||||||
|
refundPubkey = scriptarray[13]
|
||||||
# How to find redeemScript and redeemblocknum from blockchain?
|
# How to find redeemScript and redeemblocknum from blockchain?
|
||||||
print("Contract in auto redeem", contract.__dict__)
|
print("Contract in auto redeem", contract.__dict__)
|
||||||
p2sh = contract.p2sh
|
p2sh = contract.p2sh
|
||||||
|
@ -124,11 +129,11 @@ def auto_redeem(contract, secret):
|
||||||
print("Found {0} in p2sh {1}, redeeming...".format(amount, p2sh))
|
print("Found {0} in p2sh {1}, redeeming...".format(amount, p2sh))
|
||||||
|
|
||||||
# Parsing redeemblocknum from the redeemscript of the p2sh
|
# Parsing redeemblocknum from the redeemscript of the p2sh
|
||||||
redeemblocknum = find_redeemblocknum(contract)
|
# redeemblocknum = find_redeemblocknum(contract)
|
||||||
blockcount = bitcoind.getblockcount()
|
blockcount = bitcoind.getblockcount()
|
||||||
print("\nCurrent blocknum at time of redeem on Bitcoin:", blockcount)
|
print("\nCurrent blocknum at time of redeem on Bitcoin:", blockcount)
|
||||||
if blockcount < redeemblocknum:
|
if blockcount < redeemblocknum:
|
||||||
redeemPubKey = find_redeemAddr(contract)
|
# redeemPubKey = find_redeemAddr(contract)
|
||||||
print('redeemPubKey', redeemPubKey)
|
print('redeemPubKey', redeemPubKey)
|
||||||
else:
|
else:
|
||||||
print("nLocktime exceeded, refunding")
|
print("nLocktime exceeded, refunding")
|
||||||
|
@ -169,7 +174,68 @@ def auto_redeem(contract, secret):
|
||||||
else:
|
else:
|
||||||
print("No contract for this p2sh found in database", p2sh)
|
print("No contract for this p2sh found in database", p2sh)
|
||||||
|
|
||||||
|
def redeem_contract(contract, secret):
|
||||||
|
# How to find redeemScript and redeemblocknum from blockchain?
|
||||||
|
print("Contract in redeem_contract", 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
|
||||||
|
print("Found fundtx:", fundtx)
|
||||||
|
p2sh = P2SHBitcoinAddress(p2sh)
|
||||||
|
if fundtx['address'] == p2sh:
|
||||||
|
print("Found {0} in p2sh {1}, redeeming...".format(amount, p2sh))
|
||||||
|
|
||||||
|
# TODO: Decodescript is not working, add back in.
|
||||||
|
# redeemblocknum = find_redeemblocknum(contract)
|
||||||
|
|
||||||
|
blockcount = bitcoind.getblockcount()
|
||||||
|
print("\nCurrent blocknum at time of redeem on Zcash:", blockcount)
|
||||||
|
if blockcount < contract.redeemblocknum:
|
||||||
|
|
||||||
|
# redeemPubKey = find_redeemAddr(contract)
|
||||||
|
redeemPubKey = P2PKHBitcoinAddress.from_bytes(x('7788b4511a25fba1092e67b307a6dcdb6da125d9'))
|
||||||
|
|
||||||
|
print('redeemPubKey', redeemPubKey)
|
||||||
|
zec_redeemScript = CScript(x(contract.redeemScript))
|
||||||
|
txin = CMutableTxIn(fundtx['outpoint'])
|
||||||
|
txout = CMutableTxOut(fundtx['amount'] - FEE, redeemPubKey.to_scriptPubKey())
|
||||||
|
# Create the unsigned raw transaction.
|
||||||
|
tx = CMutableTransaction([txin], [txout])
|
||||||
|
sighash = SignatureHash(zec_redeemScript, tx, 0, SIGHASH_ALL)
|
||||||
|
# TODO: figure out how to better protect privkey
|
||||||
|
privkey = bitcoind.dumpprivkey(redeemPubKey)
|
||||||
|
sig = privkey.sign(sighash) + bytes([SIGHASH_ALL])
|
||||||
|
print("SECRET", secret)
|
||||||
|
preimage = b(secret)
|
||||||
|
txin.scriptSig = CScript([sig, privkey.pub, preimage, OP_TRUE, zec_redeemScript])
|
||||||
|
|
||||||
|
print("txin.scriptSig", b2x(txin.scriptSig))
|
||||||
|
txin_scriptPubKey = zec_redeemScript.to_p2sh_scriptPubKey()
|
||||||
|
print('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))))
|
||||||
|
print("TXID SUCCESSFULLY REDEEMED")
|
||||||
|
return 'redeem_tx', b2x(lx(b2x(txid)))
|
||||||
|
else:
|
||||||
|
print("nLocktime exceeded, refunding")
|
||||||
|
refundPubKey = find_refundAddr(contract)
|
||||||
|
print('refundPubKey', refundPubKey)
|
||||||
|
txid = bitcoind.sendtoaddress(refundPubKey, fundtx['amount'] - FEE)
|
||||||
|
print("Txid of refund tx:", b2x(lx(b2x(txid))))
|
||||||
|
print("TXID SUCCESSFULLY REFUNDED")
|
||||||
|
return 'refund_tx', b2x(lx(b2x(txid)))
|
||||||
|
else:
|
||||||
|
print("No contract for this p2sh found in database", p2sh)
|
||||||
|
|
||||||
# takes hex and returns array of decoded script op codes
|
# takes hex and returns array of decoded script op codes
|
||||||
|
# This seems to be a costly operation, minimize frequency of calls.
|
||||||
def parse_script(script_hex):
|
def parse_script(script_hex):
|
||||||
redeemScript = zcashd.decodescript(script_hex)
|
redeemScript = zcashd.decodescript(script_hex)
|
||||||
scriptarray = redeemScript['asm'].split(' ')
|
scriptarray = redeemScript['asm'].split(' ')
|
||||||
|
@ -216,9 +282,6 @@ def find_transaction_to_address(p2sh):
|
||||||
bitcoind.importaddress(p2sh, "", False)
|
bitcoind.importaddress(p2sh, "", False)
|
||||||
txs = bitcoind.listunspent()
|
txs = bitcoind.listunspent()
|
||||||
for tx in txs:
|
for tx in txs:
|
||||||
# print("tx addr:", tx['address'])
|
|
||||||
# print(type(tx['address']))
|
|
||||||
# print(type(p2sh))
|
|
||||||
if tx['address'] == CBitcoinAddress(p2sh):
|
if tx['address'] == CBitcoinAddress(p2sh):
|
||||||
print("Found tx to p2sh", p2sh)
|
print("Found tx to p2sh", p2sh)
|
||||||
print(tx)
|
print(tx)
|
||||||
|
|
72
cli.py
72
cli.py
|
@ -6,34 +6,44 @@ from trades import *
|
||||||
from xcat import *
|
from xcat import *
|
||||||
import ast
|
import ast
|
||||||
|
|
||||||
def find_role(contract):
|
def save_state(trade):
|
||||||
# Obviously when regtest created both addrs on same machine, role is both.
|
save(trade)
|
||||||
if parse_addrs(contract.initiator):
|
db.create
|
||||||
return 'initiator'
|
|
||||||
else:
|
|
||||||
return 'fulfiller'
|
|
||||||
|
|
||||||
def parse_addrs(address):
|
def checkSellStatus(trade):
|
||||||
if address[:1] == 'm':
|
|
||||||
status = bXcat.validateaddress(address)
|
|
||||||
else:
|
|
||||||
status = zXcat.validateaddress(address)
|
|
||||||
status = status['ismine']
|
|
||||||
print("Address {0} is mine: {1}".format(address, status))
|
|
||||||
return status
|
|
||||||
|
|
||||||
def checkSellActions(trade):
|
|
||||||
if trade.buy.get_status() == 'funded':
|
if trade.buy.get_status() == 'funded':
|
||||||
seller_redeem(trade)
|
secret = get_secret()
|
||||||
|
print("SECRET found in checksellactions", secret)
|
||||||
|
trade = seller_redeem_p2sh(trade, secret)
|
||||||
|
print("TRADE SUCCESSFULLY REDEEMED", trade)
|
||||||
|
save_state(trade)
|
||||||
elif trade.buy.get_status() == 'empty':
|
elif trade.buy.get_status() == 'empty':
|
||||||
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 trade.buy.get_status() == 'redeemed':
|
||||||
|
print("You have already redeemed the p2sh on the second chain of this trade.")
|
||||||
|
|
||||||
def checkBuyActions(trade):
|
|
||||||
|
def checkBuyStatus(trade):
|
||||||
if trade.sell.get_status() == 'funded' and trade.buy.get_status() != 'redeemed':
|
if trade.sell.get_status() == 'funded' and trade.buy.get_status() != 'redeemed':
|
||||||
print("One active trade available, fulfilling buyer contract...")
|
print("One active trade available, fulfilling buyer contract...")
|
||||||
buyer_fulfill(trade)
|
# they should calculate redeemScript for themselves
|
||||||
|
htlc = create_htlc(trade.buy.currency, trade.buy.fulfiller, trade.buy.initiator, trade.commitment, trade.buy.locktime)
|
||||||
|
print("Buyer p2sh:", htlc['p2sh'])
|
||||||
|
# If the two p2sh match...
|
||||||
|
if buyer_p2sh == contract.buy.p2sh:
|
||||||
|
fund_tx = fund_contract(trade.buy)
|
||||||
|
trade.buy.fund_tx = fund_tx
|
||||||
|
print("trade buy with redeemscript?", trade.buy.__dict__)
|
||||||
|
save_state(trade)
|
||||||
|
else:
|
||||||
|
print("Compiled p2sh for htlc does not match what seller sent.")
|
||||||
elif trade.buy.get_status() == 'redeemed':
|
elif trade.buy.get_status() == 'redeemed':
|
||||||
buyer_redeem(trade)
|
# TODO: secret parsing
|
||||||
|
# secret = parse_secret(trade.buy.currency, trade.buy.redeem_tx)
|
||||||
|
secret = get_secret()
|
||||||
|
print("Found secret", secret)
|
||||||
|
txid = auto_redeem_p2sh(trade.sell, secret)
|
||||||
|
print("TXID after buyer redeem", txid)
|
||||||
print("XCAT trade complete!")
|
print("XCAT trade complete!")
|
||||||
|
|
||||||
def instantiateTrade(trade):
|
def instantiateTrade(trade):
|
||||||
|
@ -61,7 +71,7 @@ if __name__ == '__main__':
|
||||||
hexstr = args.argument[0]
|
hexstr = args.argument[0]
|
||||||
trade = x2s(hexstr)
|
trade = x2s(hexstr)
|
||||||
trade = instantiateTrade(ast.literal_eval(trade))
|
trade = instantiateTrade(ast.literal_eval(trade))
|
||||||
db.create(trade)
|
save_state(trade)
|
||||||
# print(trade.toJ)
|
# print(trade.toJ)
|
||||||
elif command == 'exporttrade':
|
elif command == 'exporttrade':
|
||||||
trade = get_trade()
|
trade = get_trade()
|
||||||
|
@ -73,10 +83,10 @@ if __name__ == '__main__':
|
||||||
trade = instantiateTrade(trade)
|
trade = instantiateTrade(trade)
|
||||||
if find_role(trade.sell) == 'initiator':
|
if find_role(trade.sell) == 'initiator':
|
||||||
role = 'seller'
|
role = 'seller'
|
||||||
checkSellActions(trade)
|
checkSellStatus(trade)
|
||||||
else:
|
else:
|
||||||
role = 'buyer'
|
role = 'buyer'
|
||||||
checkBuyActions(trade)
|
checkBuyStatus(trade)
|
||||||
elif command == 'newtrade':
|
elif command == 'newtrade':
|
||||||
erase_trade()
|
erase_trade()
|
||||||
role = 'seller'
|
role = 'seller'
|
||||||
|
@ -92,3 +102,19 @@ if __name__ == '__main__':
|
||||||
txid = args.argument[0]
|
txid = args.argument[0]
|
||||||
trade = db.get(txid)
|
trade = db.get(txid)
|
||||||
print(x2s(b2x(trade)))
|
print(x2s(b2x(trade)))
|
||||||
|
# Ad hoc testing starts here
|
||||||
|
elif command == "step1":
|
||||||
|
erase_trade()
|
||||||
|
print("Creating new XCAT trade...")
|
||||||
|
trade = seller_initiate(Trade())
|
||||||
|
# Save it to leveldb
|
||||||
|
save_state(trade)
|
||||||
|
elif command == "step2":
|
||||||
|
trade = get_trade()
|
||||||
|
checkBuyStatus(trade)
|
||||||
|
elif command == "step3":
|
||||||
|
trade = get_trade()
|
||||||
|
checkSellStatus(trade)
|
||||||
|
elif command == "step4":
|
||||||
|
trade = get_trade()
|
||||||
|
checkBuyStatus(trade)
|
||||||
|
|
|
@ -8,9 +8,9 @@ db = plyvel.DB('/tmp/testdb', create_if_missing=True)
|
||||||
|
|
||||||
trade = get_trade()
|
trade = get_trade()
|
||||||
## txid we retrieve by
|
## txid we retrieve by
|
||||||
if trade and 'sell' in trade:
|
if trade and trade.sell:
|
||||||
if 'fund_tx' in trade['sell']:
|
if hasattr(trade.sell, 'fund_tx'):
|
||||||
txid = trade['sell']['fund_tx']
|
txid = trade.sell.fund_tx
|
||||||
|
|
||||||
# Takes object, saves json as bytes
|
# Takes object, saves json as bytes
|
||||||
def create(trade):
|
def create(trade):
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
UYH0XxCs
|
2E8ASX0w
|
|
@ -1,10 +1,11 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
class Trade(object):
|
class Trade(object):
|
||||||
def __init__(self, sell=None, buy=None):
|
def __init__(self, sell=None, buy=None, commitment=None):
|
||||||
'''Create a new trade with a sell contract and buy contract across two chains'''
|
'''Create a new trade with a sell contract and buy contract across two chains'''
|
||||||
self.sell = sell
|
self.sell = sell
|
||||||
self.buy = buy
|
self.buy = buy
|
||||||
|
self.commitment = commitment
|
||||||
|
|
||||||
def toJSON(self):
|
def toJSON(self):
|
||||||
return json.dumps(self, default=lambda o: o.__dict__,
|
return json.dumps(self, default=lambda o: o.__dict__,
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
{"buy": {"fulfiller": "tmTjZSg4pX2Us6V5HttiwFZwj464fD2ZgpY", "initiator": "tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ", "p2sh": "t2TcYheueGfEnT2SKYMLWNXKfyzrMLKc6y2", "redeemblocknum": 113817, "currency": "zcash", "amount": 1.2, "redeemScript": "63a820b67f875a86ea6be94a1f6e44857daa739df84421ba2402dd968185b7d58371b68876a9143ea29256c9d2888ca23de42a8b8e69ca2ec235b1670399bc01b17576a914c5acca6ef39c843c7a9c3ad01b2da95fe2edf5ba6888ac"}, "sell": {"fulfiller": "mrQzUGU1dwsWRx5gsKKSDPNtrsP65vCA3Z", "initiator": "myfFr5twPYNwgeXyjCmGcrzXtCmfmWXKYp", "fund_tx": "1171aeda64eff388b3568fa4675d0ca78852911109bbe42e0ef11ad6bf1b159e", "p2sh": "2NEy1foaQU3wkMu7yygW1GZYhikeSxq6ZCq", "redeemblocknum": 911, "currency": "bitcoin", "amount": 3.5, "redeemScript": "63a820b67f875a86ea6be94a1f6e44857daa739df84421ba2402dd968185b7d58371b68876a9147788b4511a25fba1092e67b307a6dcdb6da125d967028f03b17576a914c7043e62a7391596116f54f6a64c8548e97d3fd96888ac"}}
|
|
126
xcat.py
126
xcat.py
|
@ -17,11 +17,11 @@ def check_p2sh(currency, address):
|
||||||
print("Checking funds in Zcash p2sh")
|
print("Checking funds in Zcash p2sh")
|
||||||
return zXcat.check_funds(address)
|
return zXcat.check_funds(address)
|
||||||
|
|
||||||
def create_htlc(currency, funder, redeemer, secret, locktime):
|
def create_htlc(currency, funder, redeemer, commitment, locktime):
|
||||||
if currency == 'bitcoin':
|
if currency == 'bitcoin':
|
||||||
sell_p2sh = bXcat.hashtimelockcontract(funder, redeemer, secret, locktime)
|
sell_p2sh = bXcat.hashtimelockcontract(funder, redeemer, commitment, locktime)
|
||||||
else:
|
else:
|
||||||
sell_p2sh = zXcat.hashtimelockcontract(funder, redeemer, secret, locktime)
|
sell_p2sh = zXcat.hashtimelockcontract(funder, redeemer, commitment, locktime)
|
||||||
return sell_p2sh
|
return sell_p2sh
|
||||||
|
|
||||||
def fund_htlc(currency, p2sh, amount):
|
def fund_htlc(currency, p2sh, amount):
|
||||||
|
@ -30,12 +30,16 @@ def fund_htlc(currency, p2sh, amount):
|
||||||
else:
|
else:
|
||||||
txid = zXcat.fund_htlc(p2sh, amount)
|
txid = zXcat.fund_htlc(p2sh, amount)
|
||||||
return txid
|
return txid
|
||||||
|
#
|
||||||
|
# def fund_buy_contract(trade):
|
||||||
|
# buy = trade.buy
|
||||||
|
# txid = fund_htlc(buy.currency, buy.p2sh, buy.amount)
|
||||||
|
# setattr(trade.buy, 'fund_tx', txid)
|
||||||
|
# save(trade)
|
||||||
|
# return txid
|
||||||
|
|
||||||
def fund_buy_contract(trade):
|
def fund_contract(contract):
|
||||||
buy = trade.buy
|
txid = fund_htlc(contract.currency, contract.p2sh, contract.amount)
|
||||||
txid = fund_htlc(buy.currency, buy.p2sh, buy.amount)
|
|
||||||
setattr(trade.buy, 'fund_tx', txid)
|
|
||||||
save(trade)
|
|
||||||
return txid
|
return txid
|
||||||
|
|
||||||
def fund_sell_contract(trade):
|
def fund_sell_contract(trade):
|
||||||
|
@ -45,22 +49,22 @@ def fund_sell_contract(trade):
|
||||||
save(trade)
|
save(trade)
|
||||||
return txid
|
return txid
|
||||||
|
|
||||||
def create_sell_p2sh(trade, secret, locktime):
|
def create_sell_p2sh(trade, commitment, locktime):
|
||||||
# CREATE SELL CONTRACT
|
# CREATE SELL CONTRACT
|
||||||
sell = trade.sell
|
sell = trade.sell
|
||||||
contract = create_htlc(sell.currency, sell.initiator, sell.fulfiller, secret, locktime)
|
contract = create_htlc(sell.currency, sell.initiator, sell.fulfiller, commitment, locktime)
|
||||||
print("sell contract", contract)
|
print("sell contract", contract)
|
||||||
setattr(trade.sell, 'p2sh', contract['p2sh'])
|
setattr(trade.sell, 'p2sh', contract['p2sh'])
|
||||||
setattr(trade.sell, 'redeemScript', contract['redeemScript'])
|
setattr(trade.sell, 'redeemScript', contract['redeemScript'])
|
||||||
setattr(trade.sell, 'redeemblocknum', contract['redeemblocknum'])
|
setattr(trade.sell, 'redeemblocknum', contract['redeemblocknum'])
|
||||||
save(trade)
|
save(trade)
|
||||||
|
|
||||||
def create_buy_p2sh(trade, secret, locktime):
|
def create_buy_p2sh(trade, commitment, locktime):
|
||||||
## CREATE BUY CONTRACT
|
## CREATE BUY CONTRACT
|
||||||
buy = trade.buy
|
buy = trade.buy
|
||||||
print("Now creating buy contract on the {0} blockchain where you will wait for the buyer to send funds...".format(buy.currency))
|
print("Now creating buy contract on the {0} blockchain where you will wait for the buyer to send funds...".format(buy.currency))
|
||||||
print("HTLC DETAILS", buy.currency, buy.fulfiller, buy.initiator, secret, locktime)
|
print("HTLC DETAILS", buy.currency, buy.fulfiller, buy.initiator, commitment, locktime)
|
||||||
buy_contract = create_htlc(buy.currency, buy.fulfiller, buy.initiator, secret, locktime)
|
buy_contract = create_htlc(buy.currency, buy.fulfiller, buy.initiator, commitment, locktime)
|
||||||
print("Buy contract", buy_contract)
|
print("Buy contract", buy_contract)
|
||||||
|
|
||||||
setattr(trade.buy, 'p2sh', buy_contract['p2sh'])
|
setattr(trade.buy, 'p2sh', buy_contract['p2sh'])
|
||||||
|
@ -70,7 +74,7 @@ def create_buy_p2sh(trade, secret, locktime):
|
||||||
|
|
||||||
save(trade)
|
save(trade)
|
||||||
|
|
||||||
def redeem_p2sh(contract, secret):
|
def auto_redeem_p2sh(contract, secret):
|
||||||
currency = contract.currency
|
currency = contract.currency
|
||||||
if currency == 'bitcoin':
|
if currency == 'bitcoin':
|
||||||
res = bXcat.auto_redeem(contract, secret)
|
res = bXcat.auto_redeem(contract, secret)
|
||||||
|
@ -78,10 +82,20 @@ def redeem_p2sh(contract, secret):
|
||||||
res = zXcat.auto_redeem(contract, secret)
|
res = zXcat.auto_redeem(contract, secret)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def print_trade(role):
|
|
||||||
print("\nTrade status for {0}:".format(role))
|
def redeem_p2sh(contract, secret):
|
||||||
trade = get_trade()
|
currency = contract.currency
|
||||||
pprint(trade)
|
if currency == 'bitcoin':
|
||||||
|
res = bXcat.redeem_contract(contract, secret)
|
||||||
|
else:
|
||||||
|
res = zXcat.redeem_contract(contract, secret)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def parse_secret(chain, txid):
|
||||||
|
if chain == 'bitcoin':
|
||||||
|
secret = bXcat.parse_secret(txid)
|
||||||
|
else:
|
||||||
|
secret = zXcat.parse_secret(txid)
|
||||||
|
|
||||||
#### Main functions determining user flow from command line
|
#### Main functions determining user flow from command line
|
||||||
def buyer_redeem(trade):
|
def buyer_redeem(trade):
|
||||||
|
@ -104,7 +118,7 @@ def buyer_redeem(trade):
|
||||||
save(trade)
|
save(trade)
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
def seller_redeem(trade):
|
def seller_redeem_p2sh(trade, secret):
|
||||||
buy = trade.buy
|
buy = trade.buy
|
||||||
userInput.authorize_seller_redeem(buy)
|
userInput.authorize_seller_redeem(buy)
|
||||||
|
|
||||||
|
@ -113,12 +127,11 @@ def seller_redeem(trade):
|
||||||
exit()
|
exit()
|
||||||
else:
|
else:
|
||||||
# Seller redeems buyer's funded tx (contract in p2sh)
|
# Seller redeems buyer's funded tx (contract in p2sh)
|
||||||
secret = userInput.retrieve_password()
|
|
||||||
tx_type, txid = redeem_p2sh(trade.buy, secret)
|
tx_type, txid = redeem_p2sh(trade.buy, secret)
|
||||||
|
print("Setting tx_type: txid", tx_type, txid)
|
||||||
setattr(trade.buy, tx_type, txid)
|
setattr(trade.buy, tx_type, txid)
|
||||||
save(trade)
|
|
||||||
print("You have redeemed {0} {1}!".format(buy.amount, buy.currency))
|
print("You have redeemed {0} {1}!".format(buy.amount, buy.currency))
|
||||||
print_trade('seller')
|
return trade
|
||||||
|
|
||||||
def buyer_fulfill(trade):
|
def buyer_fulfill(trade):
|
||||||
buy = trade.buy
|
buy = trade.buy
|
||||||
|
@ -157,11 +170,13 @@ def seller_initiate(trade):
|
||||||
print(trade.buy.__dict__)
|
print(trade.buy.__dict__)
|
||||||
|
|
||||||
secret = userInput.create_password()
|
secret = userInput.create_password()
|
||||||
|
save_secret(secret)
|
||||||
|
hash_of_secret = sha256(secret)
|
||||||
# TODO: Implement locktimes and mock block passage of time
|
# TODO: Implement locktimes and mock block passage of time
|
||||||
sell_locktime = 5
|
sell_locktime = 5
|
||||||
buy_locktime = 10 # Must be more than first tx
|
buy_locktime = 10 # Must be more than first tx
|
||||||
print("Creating pay-to-script-hash for sell contract...")
|
print("Creating pay-to-script-hash for sell contract...")
|
||||||
create_sell_p2sh(trade, secret, sell_locktime)
|
create_sell_p2sh(trade, hash_of_secret, sell_locktime)
|
||||||
|
|
||||||
userInput.authorize_fund_sell(trade)
|
userInput.authorize_fund_sell(trade)
|
||||||
|
|
||||||
|
@ -170,68 +185,5 @@ def seller_initiate(trade):
|
||||||
|
|
||||||
create_buy_p2sh(trade, secret, buy_locktime)
|
create_buy_p2sh(trade, secret, buy_locktime)
|
||||||
|
|
||||||
|
trade.commitment = b2x(hash_of_secret)
|
||||||
return trade
|
return trade
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
print("ZEC <-> BTC XCAT (Cross-Chain Atomic Transactions)")
|
|
||||||
print("=" * 50)
|
|
||||||
|
|
||||||
trade = get_trade()
|
|
||||||
|
|
||||||
if trade == None:
|
|
||||||
htlcTrade = Trade()
|
|
||||||
print("New empty trade")
|
|
||||||
else:
|
|
||||||
buy = Contract(trade['buy'])
|
|
||||||
sell = Contract(trade['sell'])
|
|
||||||
htlcTrade = Trade(buy=buy, sell=sell)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if sys.argv[1] == 'new':
|
|
||||||
erase_trade()
|
|
||||||
role = 'seller'
|
|
||||||
htlcTrade = Trade()
|
|
||||||
print("Creating new XCAT transaction...")
|
|
||||||
else:
|
|
||||||
role = sys.argv[1]
|
|
||||||
print("Your role in demo:", role)
|
|
||||||
except:
|
|
||||||
if trade == None:
|
|
||||||
print("No active trades available.")
|
|
||||||
res = input("Would you like to initiate a trade? (y/n) ")
|
|
||||||
if res == 'y':
|
|
||||||
role = 'seller'
|
|
||||||
else:
|
|
||||||
exit()
|
|
||||||
else:
|
|
||||||
print("Trade exists, run script as buyer or seller to complete trade.")
|
|
||||||
exit()
|
|
||||||
|
|
||||||
if htlcTrade.buy is not None and htlcTrade.sell is not None:
|
|
||||||
if htlcTrade.sell.get_status() == 'redeemed' and htlcTrade.buy.get_status() == 'redeemed':
|
|
||||||
print("This trade is already complete! Trade details:")
|
|
||||||
pprint(trade)
|
|
||||||
exit()
|
|
||||||
|
|
||||||
if role == "seller":
|
|
||||||
if htlcTrade.sell == None:
|
|
||||||
seller_initiate(htlcTrade)
|
|
||||||
elif htlcTrade.buy.get_status() == 'funded':
|
|
||||||
seller_redeem(htlcTrade)
|
|
||||||
elif htlcTrade.buy.get_status() == 'empty':
|
|
||||||
print("Buyer has not yet funded the contract where you offered to buy {0}, please wait for them to complete their part.".format(htlcTrade.buy.currency))
|
|
||||||
else:
|
|
||||||
# Need better way of preventing buyer from having secret
|
|
||||||
# if 'status' not in trade['buy'] and trade['sell']['status'] == 'funded':
|
|
||||||
if htlcTrade.sell.get_status() == 'funded' and htlcTrade.buy.get_status() != 'redeemed':
|
|
||||||
print("One active trade available, fulfilling buyer contract...")
|
|
||||||
buyer_fulfill(htlcTrade)
|
|
||||||
# How to monitor if txs are included in blocks -- should use blocknotify and a monitor daemon?
|
|
||||||
# p2sh = trade['buy']['p2sh']
|
|
||||||
# check_blocks(p2sh)
|
|
||||||
elif htlcTrade.buy.get_status() == 'redeemed':
|
|
||||||
# Seller has redeemed buyer's tx, buyer can now redeem.
|
|
||||||
buyer_redeem(htlcTrade)
|
|
||||||
print("XCAT trade complete!")
|
|
||||||
|
|
||||||
# Note: there is some little endian weirdness in the bXcat and zXcat files, need to handle the endianness of txids better & more consistently
|
|
||||||
|
|
70
zXcat.py
70
zXcat.py
|
@ -36,16 +36,16 @@ def get_keys(funder_address, redeemer_address):
|
||||||
def privkey(address):
|
def privkey(address):
|
||||||
zcashd.dumpprivkey(address)
|
zcashd.dumpprivkey(address)
|
||||||
|
|
||||||
def hashtimelockcontract(funder, redeemer, secret, locktime):
|
def hashtimelockcontract(funder, redeemer, commitment, locktime):
|
||||||
funderAddr = CBitcoinAddress(funder)
|
funderAddr = CBitcoinAddress(funder)
|
||||||
redeemerAddr = CBitcoinAddress(redeemer)
|
redeemerAddr = CBitcoinAddress(redeemer)
|
||||||
h = sha256(secret)
|
# h = sha256(secret)
|
||||||
blocknum = zcashd.getblockcount()
|
blocknum = zcashd.getblockcount()
|
||||||
print("Current blocknum", blocknum)
|
print("Current blocknum", blocknum)
|
||||||
redeemblocknum = blocknum + locktime
|
redeemblocknum = blocknum + locktime
|
||||||
print("REDEEMBLOCKNUM ZCASH", redeemblocknum)
|
print("REDEEMBLOCKNUM 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 (as raw hex/bin data?), and can rm last op_equalverify (for direct pubkey comparison)
|
||||||
zec_redeemScript = CScript([OP_IF, OP_SHA256, h, OP_EQUALVERIFY,OP_DUP, OP_HASH160,
|
zec_redeemScript = CScript([OP_IF, OP_SHA256, x(commitment), OP_EQUALVERIFY,OP_DUP, OP_HASH160,
|
||||||
redeemerAddr, OP_ELSE, redeemblocknum, OP_CHECKLOCKTIMEVERIFY, OP_DROP, OP_DUP, OP_HASH160,
|
redeemerAddr, OP_ELSE, redeemblocknum, OP_CHECKLOCKTIMEVERIFY, OP_DROP, OP_DUP, OP_HASH160,
|
||||||
funderAddr, OP_ENDIF,OP_EQUALVERIFY, OP_CHECKSIG])
|
funderAddr, OP_ENDIF,OP_EQUALVERIFY, OP_CHECKSIG])
|
||||||
print("Redeem script for p2sh contract on Zcash blockchain:", b2x(zec_redeemScript))
|
print("Redeem script for p2sh contract on Zcash blockchain:", b2x(zec_redeemScript))
|
||||||
|
@ -77,7 +77,7 @@ def get_tx_details(txid):
|
||||||
|
|
||||||
def find_transaction_to_address(p2sh):
|
def find_transaction_to_address(p2sh):
|
||||||
zcashd.importaddress(p2sh, "", False)
|
zcashd.importaddress(p2sh, "", False)
|
||||||
txs = zcashd.listunspent()
|
txs = zcashd.listunspent(0, 100)
|
||||||
for tx in txs:
|
for tx in txs:
|
||||||
# print("tx addr:", tx['address'])
|
# print("tx addr:", tx['address'])
|
||||||
# print(type(tx['address']))
|
# print(type(tx['address']))
|
||||||
|
@ -128,7 +128,6 @@ def parse_secret(txid):
|
||||||
|
|
||||||
# redeems automatically after buyer has funded tx, by scanning for transaction to the p2sh
|
# redeems automatically after buyer has funded tx, by scanning for transaction to the p2sh
|
||||||
# i.e., doesn't require buyer telling us fund txid
|
# i.e., doesn't require buyer telling us fund txid
|
||||||
|
|
||||||
def auto_redeem(contract, secret):
|
def auto_redeem(contract, secret):
|
||||||
# How to find redeemScript and redeemblocknum from blockchain?
|
# How to find redeemScript and redeemblocknum from blockchain?
|
||||||
print("Contract in auto redeem", contract.__dict__)
|
print("Contract in auto redeem", contract.__dict__)
|
||||||
|
@ -187,13 +186,74 @@ def auto_redeem(contract, secret):
|
||||||
else:
|
else:
|
||||||
print("No contract for this p2sh found in database", p2sh)
|
print("No contract for this p2sh found in database", p2sh)
|
||||||
|
|
||||||
|
def redeem_contract(contract, secret):
|
||||||
|
# How to find redeemScript and redeemblocknum from blockchain?
|
||||||
|
print("Contract in redeem contract", 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
|
||||||
|
print("Found fundtx:", fundtx)
|
||||||
|
p2sh = P2SHBitcoinAddress(p2sh)
|
||||||
|
if fundtx['address'] == p2sh:
|
||||||
|
print("Found {0} in p2sh {1}, redeeming...".format(amount, p2sh))
|
||||||
|
|
||||||
|
# Where can you find redeemblocknum in the transaction?
|
||||||
|
# redeemblocknum = find_redeemblocknum(contract)
|
||||||
|
blockcount = zcashd.getblockcount()
|
||||||
|
print("\nCurrent blocknum at time of redeem on Zcash:", blockcount)
|
||||||
|
if blockcount < contract.d:
|
||||||
|
# TODO: parse the script once, up front.
|
||||||
|
redeemPubKey = find_redeemAddr(contract)
|
||||||
|
|
||||||
|
|
||||||
|
print('redeemPubKey', redeemPubKey)
|
||||||
|
zec_redeemScript = CScript(x(contract.redeemScript))
|
||||||
|
txin = CMutableTxIn(fundtx['outpoint'])
|
||||||
|
txout = CMutableTxOut(fundtx['amount'] - FEE, redeemPubKey.to_scriptPubKey())
|
||||||
|
# Create the unsigned raw transaction.
|
||||||
|
tx = CMutableTransaction([txin], [txout])
|
||||||
|
sighash = SignatureHash(zec_redeemScript, tx, 0, SIGHASH_ALL)
|
||||||
|
# TODO: figure out how to better protect privkey
|
||||||
|
privkey = zcashd.dumpprivkey(redeemPubKey)
|
||||||
|
sig = privkey.sign(sighash) + bytes([SIGHASH_ALL])
|
||||||
|
print("SECRET", secret)
|
||||||
|
preimage = secret.encode('utf-8')
|
||||||
|
txin.scriptSig = CScript([sig, privkey.pub, preimage, OP_TRUE, zec_redeemScript])
|
||||||
|
|
||||||
|
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 = zcashd.sendrawtransaction(tx)
|
||||||
|
print("Txid of submitted redeem tx: ", b2x(lx(b2x(txid))))
|
||||||
|
print("TXID SUCCESSFULLY REDEEMED")
|
||||||
|
return 'redeem_tx', b2x(lx(b2x(txid)))
|
||||||
|
else:
|
||||||
|
print("nLocktime exceeded, refunding")
|
||||||
|
refundPubKey = find_refundAddr(contract)
|
||||||
|
print('refundPubKey', refundPubKey)
|
||||||
|
txid = zcashd.sendtoaddress(refundPubKey, fundtx['amount'] - FEE)
|
||||||
|
print("Txid of refund tx:", b2x(lx(b2x(txid))))
|
||||||
|
print("TXID SUCCESSFULLY REFUNDED")
|
||||||
|
return 'refund_tx', b2x(lx(b2x(txid)))
|
||||||
|
else:
|
||||||
|
print("No contract for this p2sh found in database", p2sh)
|
||||||
|
|
||||||
def parse_script(script_hex):
|
def parse_script(script_hex):
|
||||||
redeemScript = zcashd.decodescript(script_hex)
|
redeemScript = zcashd.decodescript(script_hex)
|
||||||
scriptarray = redeemScript['asm'].split(' ')
|
scriptarray = redeemScript['asm'].split(' ')
|
||||||
return scriptarray
|
return scriptarray
|
||||||
|
|
||||||
def find_redeemblocknum(contract):
|
def find_redeemblocknum(contract):
|
||||||
|
print("In find_redeemblocknum")
|
||||||
scriptarray = parse_script(contract.redeemScript)
|
scriptarray = parse_script(contract.redeemScript)
|
||||||
|
print("Returning scriptarray", scriptarray)
|
||||||
redeemblocknum = scriptarray[8]
|
redeemblocknum = scriptarray[8]
|
||||||
return int(redeemblocknum)
|
return int(redeemblocknum)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue