Merge pull request #5 from zcash/refactor-automate

Refactor automate
This commit is contained in:
arcalinea 2017-06-15 11:00:06 -07:00 committed by GitHub
commit 158519ae0b
18 changed files with 682 additions and 418 deletions

View File

@ -1,15 +1,14 @@
# ZBXCAT
A work-in-progress for Zcash Bitcoin Cross-Chain Atomic Transactions
Contains basic scripts we're still testing in regtest mode on both networks. This may all be refactored as we go.
Bitcoin scripts use the rpc proxy code in python-bitcoinlib, and Zcash script will use python-zcashlib (a Zcash fork of python-bitcoinlib).
## BTC p2sh HTLC script
## Setup
The script `btc-p2sh-htlc.py` creates and redeems a p2sh transaction on Bitcoin regtest using a preimage. TODO: Locktime scripting still needs work.
To successfully run it, you'll need python3, the dependencies installed, and a bitcoin daemon running in regtest mode.
To successfully run this, you'll need python3, the dependencies installed, and a bitcoin daemon running in regtest mode.
To install python3 in a virtualenv, run this command from the top level of the directory:
```
@ -22,15 +21,6 @@ To install dependencies, run:
pip install -r requirements.txt
```
To run a bitcoin daemon in regtest mode, with the ability to inspect transactions outside your wallet (useful for testing purposes), use the command
```
bitcoind -regtest -txindex=1 --daemon
```
## ZEC p2sh HTLC script
The script `zec-p2sh-htlc.py` is the same as the BTC script, but uses python-zcashlib, which is still a work in progress.
To install python-zcashlib for testing and editing, clone the repository to your local filesystem. It is currently on a branch of python-bitcoinlib maintained by @arcalinea.
```
@ -45,13 +35,26 @@ To install python-zcashlib from your local filesystem path in editable mode:
`pip install --editable (-e) <path-to-zcashlib-fork-of-python-bitcoinlib>`
## Run Zcash and Bitcoin daemons locally
To test, run a Zcash daemon and bitcoin daemon in regtest mode. You may have to change the port one of them runs on, for example with the flag `-port=18445`.
To run a bitcoin daemon in regtest mode, with the ability to inspect transactions outside your wallet (useful for testing purposes), use the command
```
bitcoind -regtest -txindex=1 -daemon -port=18445
```
Be sure to run a Zcash daemon in regtest mode.
```
zcashd -regtest -txindex=1 --daemon
```
## XCAT CLI interface
Run `xcat.py` to go through the protocol. `xcat.py new` creats a new trade, `xcat.py seller` progresses with the seller's next step, `xcat.py buyer` progresses with the buyer's next step.
To test the entire script workflow, run `test.py`.
## Misc
`protocol-pseudocode.py` is guaranteed to not run. It was written as a brainstorm/sketch of a hypothetical xcat protocol using @ebfull's fork of Zcash/Bitcoin that supports createhtlc as an rpc command. Including here in case it's useful in any way.
I used the module [future](http://python-future.org/futurize.html) to make existing python2 code for the rpc interface compatible with python3.

168
bXcat.py
View File

@ -12,7 +12,7 @@ from bitcoin.core import b2x, lx, b2lx, x, COIN, COutPoint, CMutableTxOut, CMuta
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
from bitcoin.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH
from bitcoin.wallet import CBitcoinAddress, CBitcoinSecret
from bitcoin.wallet import CBitcoinAddress, CBitcoinSecret, P2SHBitcoinAddress, P2PKHBitcoinAddress
from utils import *
@ -27,6 +27,13 @@ FEE = 0.001*COIN
zcashd = zcash.rpc.Proxy()
def parse_secret(txid):
decoded = bitcoind.getrawtransaction(lx(txid), 1)
print("Decoded", decoded)
# decoded = bitcoind.decoderawtransaction(raw)
asm = decoded['vin'][0]['scriptSig']['asm'].split(" ")
print(asm[2])
def get_keys(funder_address, redeemer_address):
fundpubkey = CBitcoinAddress(funder_address)
redeempubkey = CBitcoinAddress(redeemer_address)
@ -42,18 +49,21 @@ def hashtimelockcontract(funder, redeemer, secret, locktime):
redeemerAddr = CBitcoinAddress(redeemer)
h = sha256(secret)
blocknum = bitcoind.getblockcount()
print("Current blocknum", blocknum)
redeemblocknum = blocknum + locktime
zec_redeemScript = CScript([OP_IF, OP_SHA256, h, OP_EQUALVERIFY,OP_DUP, OP_HASH160,
print("REDEEMBLOCKNUM BITCOIN", redeemblocknum)
redeemScript = CScript([OP_IF, OP_SHA256, h, 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])
txin_scriptPubKey = zec_redeemScript.to_p2sh_scriptPubKey()
print("Redeem script for p2sh contract on Bitcoin blockchain:", b2x(redeemScript))
txin_scriptPubKey = redeemScript.to_p2sh_scriptPubKey()
# Convert the P2SH scriptPubKey to a base58 Bitcoin address
txin_p2sh_address = CBitcoinAddress.from_scriptPubKey(txin_scriptPubKey)
p2sh = str(txin_p2sh_address)
return {'p2sh': p2sh, 'redeemblocknum': redeemblocknum, 'zec_redeemScript': b2x(zec_redeemScript), 'redeemer': redeemer, 'funder': funder}
return {'p2sh': p2sh, 'redeemblocknum': redeemblocknum, 'redeemScript': b2x(redeemScript), 'redeemer': redeemer, 'funder': funder}
def fund_htlc(p2sh, amount):
send_amount = amount*COIN
send_amount = float(amount) * COIN
fund_txid = bitcoind.sendtoaddress(p2sh, send_amount)
txid = b2x(lx(b2x(fund_txid)))
return txid
@ -87,75 +97,135 @@ def search_p2sh(block, p2sh):
print("Address to p2sh found in transaction!", addr)
print("Returning from search_p2sh")
def get_tx_details(txid):
# must convert txid string to bytes x(txid)
fund_txinfo = bitcoind.gettransaction(lx(txid))
return fund_txinfo['details'][0]
def redeem(p2sh, action):
# ensure p2sh is imported
bitcoind.importaddress(p2sh, '', False)
# 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(contract, 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
print("Found fundtx:", fundtx)
p2sh = P2SHBitcoinAddress(p2sh)
if fundtx['address'] == p2sh:
print("Found {0} in p2sh {1}, redeeming...".format(amount, p2sh))
contracts = get_contract()
trade = get_trade()
for key in contracts:
if key == p2sh:
contract = contracts[key]
if contract:
print("Redeeming tx in p2sh", p2sh)
# TODO: Have to get tx info from saved contract p2sh
redeemblocknum = contract['redeemblocknum']
zec_redeemScript = contract['zec_redeemScript']
# Parsing redeemblocknum from the redeemscript of the p2sh
redeemblocknum = find_redeemblocknum(contract)
blockcount = bitcoind.getblockcount()
print("\nCurrent blocknum at time of redeem on Bitcoin:", 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()
txid = trade[action]['fund_tx']
details = get_tx_details(txid)
print("Txid for fund tx", txid)
# must be little endian hex
txin = CMutableTxIn(COutPoint(lx(txid), details['vout']))
redeemPubKey = CBitcoinAddress(contract['redeemer'])
amount = trade[action]['amount'] * COIN
print("amount: {0}, fee: {1}".format(amount, FEE))
txout = CMutableTxOut(amount - FEE, redeemPubKey.to_scriptPubKey())
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...
# Is stored as hex, must convert to bytes
zec_redeemScript = CScript(x(zec_redeemScript))
tx.nLockTime = redeemblocknum
if blockcount >= redeemblocknum:
print("\nLocktime exceeded")
tx.nLockTime = redeemblocknum # Ariel: This is only needed when redeeming with the timelock
sighash = SignatureHash(zec_redeemScript, tx, 0, SIGHASH_ALL)
# TODO: figure out how to better protect privkey?
# TODO: figure out how to better protect privkey
privkey = bitcoind.dumpprivkey(redeemPubKey)
sig = privkey.sign(sighash) + bytes([SIGHASH_ALL])
# TODO: Figure out where to store secret preimage securely. Parse from scriptsig of redeemtx
secret = trade['sell']['secret']
print("SECRET", secret)
preimage = secret.encode('utf-8')
print('preimage', preimage)
# print('zec_redeemScript', zec_redeemScript)
txin.scriptSig = CScript([sig, privkey.pub, preimage, OP_TRUE, zec_redeemScript])
# print("Redeem tx hex:", b2x(tx.serialize()))
# Can only call to_p2sh_scriptPubKey on CScript obj
# exit()
print("txin.scriptSig", b2x(txin.scriptSig))
txin_scriptPubKey = zec_redeemScript.to_p2sh_scriptPubKey()
# print("txin.scriptSig", b2x(txin.scriptSig))
# print("txin_scriptPubKey", b2x(txin_scriptPubKey))
# print('tx', tx)
print('Redeem txhex', b2x(tx.serialize()))
VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,))
print("Script verified, sending raw tx...")
print("Raw tx of prepared redeem tx: ", b2x(tx.serialize()))
print("script verified, sending raw tx")
txid = bitcoind.sendrawtransaction(tx)
txhex = b2x(lx(b2x(txid)))
print("Txid of submitted redeem tx: ", txhex)
return txhex
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)
# takes hex and returns array of decoded script op codes
def parse_script(script_hex):
redeemScript = zcashd.decodescript(script_hex)
scriptarray = redeemScript['asm'].split(' ')
return scriptarray
def find_redeemblocknum(contract):
scriptarray = parse_script(contract.redeemScript)
redeemblocknum = scriptarray[8]
return int(redeemblocknum)
def find_redeemAddr(contract):
scriptarray = parse_script(contract.redeemScript)
redeemer = scriptarray[6]
redeemAddr = P2PKHBitcoinAddress.from_bytes(x(redeemer))
return redeemAddr
def find_refundAddr(contract):
scriptarray = parse_script(contract.redeemScript)
funder = scriptarray[13]
refundAddr = P2PKHBitcoinAddress.from_bytes(x(funder))
return refundAddr
# def find_recipient(contract):
# initiator = CBitcoinAddress(contract.initiator)
# fulfiller = CBitcoinAddress(contract.fulfiller)
# print("Initiator", b2x(initiator))
# print("Fulfiler", b2x(fulfiller))
# make this dependent on actual fund tx to p2sh, not contract
# print("Contract fund_tx", contract.fund_tx)
# txid = contract.fund_tx
# raw = bitcoind.gettransaction(lx(txid))['hex']
# print("Raw tx", raw)
# # print("Raw", raw)
# decoded = zcashd.decoderawtransaction(raw + '00')
# scriptSig = decoded['vin'][0]['scriptSig']
# print("Decoded", scriptSig)
# asm = scriptSig['asm'].split(" ")
# pubkey = asm[1]
# print('pubkey', pubkey)
# redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey))
# print('redeemPubkey', redeemPubkey)
def find_transaction_to_address(p2sh):
bitcoind.importaddress(p2sh, "", False)
txs = bitcoind.listunspent()
for tx in txs:
# print("tx addr:", tx['address'])
# print(type(tx['address']))
# print(type(p2sh))
if tx['address'] == CBitcoinAddress(p2sh):
print("Found tx to p2sh", p2sh)
print(tx)
return tx
def new_bitcoin_addr():
addr = bitcoind.getnewaddress()
print('new btc addr', addr.to_scriptPubKey)
return addr.to_scriptPubKey()
def generate(num):
blocks = bitcoind.generate(num)
return blocks

View File

@ -1,15 +0,0 @@
#!/usr/bin/env python3
import sys
import subprocess
subprocess.Popen("source /home/jay/Zcash/python3-xcat/venv/bin/activate", shell=True)
import bitcoin
import bitcoin.rpc
SelectParams('regtest')
bitcoind = bitcoin.rpc.Proxy()
txid = sys.argv[1]
print("Incoming txid:", txid)
tx = bitcoind.gettransaction(txid, 0)
print(tx)

View File

@ -1 +0,0 @@
{"t2QrLFUqmp1v1xQSE3hmgwcYuinRb3BRWMm": {"funder": "tmTjZSg4pX2Us6V5HttiwFZwj464fD2ZgpY", "redeemer": "tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ", "redeemblocknum": 182, "zec_redeemScript": "63a820343e398e4e99a68e7de6ec57f00b6a14a8e6d0a7dd714efbab4dcbd385f4f3038876a9143ea29256c9d2888ca23de42a8b8e69ca2ec235b16702b600b17576a914c5acca6ef39c843c7a9c3ad01b2da95fe2edf5ba6888ac", "p2sh": "t2QrLFUqmp1v1xQSE3hmgwcYuinRb3BRWMm"}, "2MuWU5BpLpqJvvzkCPq8gFHA4VFFGyvjaJf": {"funder": "myfFr5twPYNwgeXyjCmGcrzXtCmfmWXKYp", "redeemer": "mrQzUGU1dwsWRx5gsKKSDPNtrsP65vCA3Z", "redeemblocknum": 146, "zec_redeemScript": "63a820343e398e4e99a68e7de6ec57f00b6a14a8e6d0a7dd714efbab4dcbd385f4f3038876a9147788b4511a25fba1092e67b307a6dcdb6da125d967029200b17576a914c7043e62a7391596116f54f6a64c8548e97d3fd96888ac", "p2sh": "2MuWU5BpLpqJvvzkCPq8gFHA4VFFGyvjaJf"}}

View File

@ -1,8 +0,0 @@
class Contract(object):
def __init__(self, funder=None, redeemer=None, blockchain=None):
'''Create a new hash time-locked contract'''
self.funder = funder
self.redeemer = redeemer
self.blockchain = blockchain

View File

@ -1,70 +0,0 @@
from rpc.ZDaemon import *
import argparse, textwrap
import hashlib
# How to prevent this program from having access to local bitcoind or zcashd? Have to just allow it?
# A bit like a GUI wallet that you download, then use as a local app? Running its own instance of zcashd, bitcoind? (bitcoin SPV client possible?)
#
# UI: Seller puts in their address, buyer puts in theirs. (Job of DEX exchange can be to match orders...)
# Interface extracts pubkeys
# Interface determines locktime, and gives seller their "secret redeem code" for their funds (hash preimage)
# Interface creates the HTLC with preimage
# Interface imports address for the redeem script, for buyer, and for seller. (needs access to local zcashd/bitcoind)
# Buyer sends to p2sh address, funding
# Interface checks that it's in wallet, then creates the redeem transaction (rawtx) for buyer and seller
# After set time passes, interface tries to complete the transaction
# If falls through, uses redeem transactions.
zd = ZDaemon(network=TESTNET)
def get_pubkey_from_taddr():
taddr = raw_input("Enter the taddr you would like to send from: ")
resp = zd.validateaddress(taddr)
if resp['pubkey']:
pubkey = resp['pubkey']
print "The pubkey for the address you entered is: ", pubkey
return pubkey
def create_htlc(pubkey, sellerpubkey):
# UI is going to be opinionated about timelocks, and provide secret for you.
secret = raw_input("Enter a secret to lock your funds: ")
# convert to bytes
secret_bytes = str.encode(secret)
digest = hashlib.sha256(preimage).digest()
time = 10
# need to add this rpc call, assume is running zcashd on zkcp branch
htlc = zd.createhtlc(pubkey, sellerpubkey, secret_bytes, time)
return htlc
def import_address(htlc):
fund_addr = zd.importaddress(htlc['redeemScript'])
txs = zd.listunspent()
for tx in txs:
if tx['address'] == htlc['address']
return tx['address']
def fund_p2sh(p2sh):
fund_tx = zd.sendtoaddress(p2sh)
return fund_tx
def redeem_p2sh(fund_tx):
for tx in txs:
if tx['address'] == htlc['address']
return tx['address']
return tx['txid'], tx['vout']
# write this function too
rawtx = zd.createrawtransaction(txid, vout, selleraddress, amount)
# Buyer has to sign raw tx
# out of band: sellerpubkey, selleraddress
pubkey = get_pubkey_from_taddr()
# Wait for pubkey from buyer. Assume some messaging layer, or out of band communication, with seller.
htlc = create_htlc(pubkey, sellerpubkey)
# import address for both buyer and seller
addr = import_address(htlc)
# Buyer funds tx
fund_tx = fund_p2sh(addr)
# Now seller must redeem

1
secret.json Normal file
View File

@ -0,0 +1 @@
Hm0Mla5n

103
test.py Normal file
View File

@ -0,0 +1,103 @@
import zXcat
import bXcat
from xcat import *
htlcTrade = Trade()
print("Starting test of xcat...")
def get_initiator_addresses():
return {'bitcoin': 'myfFr5twPYNwgeXyjCmGcrzXtCmfmWXKYp', 'zcash': 'tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ'}
def get_fulfiller_addresses():
return {'bitcoin': 'mrQzUGU1dwsWRx5gsKKSDPNtrsP65vCA3Z', 'zcash': 'tmTjZSg4pX2Us6V5HttiwFZwj464fD2ZgpY'}
def initiate(trade):
# Get amounts
amounts = {"sell": {"currency": "bitcoin", "amount": "0.5"}, "buy": {"currency": "zcash", "amount": "1.12"}}
sell = amounts['sell']
buy = amounts['buy']
sell_currency = sell['currency']
buy_currency = buy['currency']
# Get addresses
init_addrs = get_initiator_addresses()
sell['initiator'] = init_addrs[sell_currency]
buy['initiator'] = init_addrs[buy_currency]
fulfill_addrs = get_fulfiller_addresses()
sell['fulfiller'] = fulfill_addrs[sell_currency]
buy['fulfiller'] = fulfill_addrs[buy_currency]
# initializing contract classes with addresses, currencies, and amounts
trade.sellContract = Contract(sell)
trade.buyContract = Contract(buy)
print(trade.sellContract.__dict__)
print(trade.buyContract.__dict__)
secret = generate_password()
print("Generating secret to lock funds:", secret)
save_secret(secret)
# TODO: Implement locktimes and mock block passage of time
sell_locktime = 2
buy_locktime = 4 # Must be more than first tx
create_sell_p2sh(trade, secret, sell_locktime)
txid = fund_sell_contract(trade)
print("Sent")
create_buy_p2sh(trade, secret, buy_locktime)
def fulfill(trade):
buy = trade.buyContract
sell = trade.sellContract
buy_p2sh_balance = check_p2sh(buy.currency, buy.p2sh)
sell_p2sh_balance = check_p2sh(sell.currency, sell.p2sh)
if buy_p2sh_balance == 0:
print("Buy amt:", buy.amount)
txid = fund_buy_contract(trade)
print("Fund tx txid:", txid)
else:
raise ValueError("Sell p2sh not funded, buyer cannot redeem")
def redeem_one(trade):
buy = trade.buyContract
if trade.sellContract.get_status() == 'redeemed':
raise RuntimeError("Sell contract status was already redeemed before seller could redeem buyer's tx")
else:
secret = get_secret()
print("GETTING SECRET IN TEST:", secret)
tx_type, txid = redeem_p2sh(trade.buyContract, secret)
print("\nTX Type", tx_type)
setattr(trade.buyContract, tx_type, txid)
save(trade)
print("You have redeemed {0} {1}!".format(buy.amount, buy.currency))
def redeem_two(trade):
if trade.sellContract.get_status() == 'redeemed':
raise RuntimeError("Sell contract was redeemed before buyer could retrieve funds")
elif trade.buyContract.get_status() == 'refunded':
print("buyContract was refunded to buyer")
else:
# Buy contract is where seller disclosed secret in redeeming
if trade.buyContract.currency == 'bitcoin':
secret = bXcat.parse_secret(trade.buyContract.redeem_tx)
else:
secret = zXcat.parse_secret(trade.buyContract.redeem_tx)
print("Found secret in seller's redeem tx", secret)
redeem_tx = redeem_p2sh(trade.sellContract, secret)
setattr(trade.sellContract, 'redeem_tx', redeem_tx)
save(trade)
def generate_blocks(num):
bXcat.generate(num)
zXcat.generate(num)
initiate(htlcTrade)
fulfill(htlcTrade)
generate_blocks(6)
redeem_one(htlcTrade)
redeem_two(htlcTrade)
# addr = CBitcoinAddress('tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ')
# print(addr)
# # print(b2x('tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ'))
# print(b2x(addr))

27
trades.py Normal file
View File

@ -0,0 +1,27 @@
class Trade(object):
def __init__(self, sellContract=None, buyContract=None):
'''Create a new trade with a sell contract and buy contract across two chains'''
self.sellContract = sellContract
self.buyContract = buyContract
class Contract(object):
def __init__(self, data):
# Keep track of funding and redeem tx?
allowed = ('fulfiller', 'initiator', 'currency', 'p2sh', 'amount', 'fund_tx', 'redeem_tx', 'secret', 'redeemScript', 'redeemblocknum')
for key in data:
if key in allowed:
setattr(self, key, data[key])
def get_status(self):
# keep as function or set as property?
if hasattr(self, 'redeem_tx'):
return 'redeemed'
elif hasattr(self, 'refund_tx'):
return 'refunded'
elif hasattr(self, 'fund_tx'):
# Do additional validation here to check amts on blockchain
return 'funded'
else:
return 'empty'
# other classes; transactions? users?

View File

79
userInput.py Normal file
View File

@ -0,0 +1,79 @@
from utils import *
def get_trade_amounts():
print("in user input")
amounts = {}
sell_currency = input("Which currency would you like to trade out of (bitcoin or zcash)? ")
if sell_currency == '':
sell_currency = 'bitcoin'
if sell_currency == 'bitcoin':
buy_currency = 'zcash'
else:
buy_currency = 'bitcoin'
print(sell_currency)
sell_amt = input("How much {0} do you want to sell? ".format(sell_currency))
sell_amt = 3.5
print(sell_amt)
buy_amt = input("How much {0} do you want to receive in exchange? ".format(buy_currency))
buy_amt = 1.2
print(buy_amt)
sell = {'currency': sell_currency, 'amount': sell_amt}
buy = {'currency': buy_currency, 'amount': buy_amt}
amounts['sell'] = sell
amounts['buy'] = buy
return amounts
def create_password():
secret = input("Initiating trade: Create a password to place the funds in escrow: ")
# TODO: hash and store secret only locally.
if secret == '':
secret = generate_password()
print('Remember your password:', secret)
# Saving secret locally for now
save_secret(secret)
return secret
def retrieve_password():
secret = input("Enter the secret you used to lock the funds in order to redeem:")
if secret == '':
secret = get_secret()
print(secret)
return secret
def authorize_fund_sell(htlcTrade):
print('To complete your sell, send {0} {1} to this p2sh: {2}'.format(htlcTrade.sellContract.amount, htlcTrade.sellContract.currency, htlcTrade.sellContract.p2sh))
response = input("Type 'enter' to allow this program to send funds on your behalf.")
def get_initiator_addresses():
btc_addr = input("Enter your bitcoin address: ")
# btc_addr = bXcat.new_bitcoin_addr()
btc_addr = 'myfFr5twPYNwgeXyjCmGcrzXtCmfmWXKYp'
print(btc_addr)
zec_addr = input("Enter your zcash address: ")
# zec_addr = zXcat.new_zcash_addr()
zec_addr = 'tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ'
print(zec_addr)
addresses = {'bitcoin': btc_addr, 'zcash': zec_addr}
return addresses
def get_fulfiller_addresses():
btc_addr = input("Enter the bitcoin address of the party you want to trade with: ")
# btc_addr = bXcat.new_bitcoin_addr()
btc_addr = 'mrQzUGU1dwsWRx5gsKKSDPNtrsP65vCA3Z'
print(btc_addr)
zec_addr = input("Enter the zcash address of the party you want to trade with: ")
# zec_addr = zXcat.new_zcash_addr()
zec_addr = 'tmTjZSg4pX2Us6V5HttiwFZwj464fD2ZgpY'
print(zec_addr)
addresses = {'bitcoin': btc_addr, 'zcash': zec_addr}
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))
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):
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):
input("Seller funded the contract where you paid them in {0} to buy {1}, type 'enter' to redeem {2} {1} from {3}.".format(trade.buyContract.currency, trade.sellContract.currency, trade.sellContract.amount, trade.sellContract.p2sh))

View File

@ -1,11 +1,22 @@
import hashlib
import json
import random
import binascii
def hex2str(hexstring):
return binascii.unhexlify(hexstring).decode('utf-8')
def sha256(secret):
preimage = secret.encode('utf8')
h = hashlib.sha256(preimage).digest()
return h
def generate_password():
s = "abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"
passlen = 8
p = "".join(random.sample(s,passlen))
return p
# TODO: Port these over to leveldb or some other database
def save_trade(trade):
with open('xcat.json', 'w') as outfile:
@ -23,11 +34,20 @@ def erase_trade():
with open('xcat.json', 'w') as outfile:
outfile.write('')
def get_contract():
with open('contract.json') as data_file:
contractdb = json.load(data_file)
return contractdb
# caching the secret locally for now...
def get_secret():
with open('secret.json') as data_file:
for line in data_file:
return line.strip('\n')
def save_contract(contracts):
with open('contract.json', 'w') as outfile:
json.dump(contracts, outfile)
def save_secret(secret):
with open('secret.json', 'w') as outfile:
outfile.write(secret)
def save(trade):
print("Saving trade")
trade = {
'sell': trade.sellContract.__dict__,
'buy': trade.buyContract.__dict__
}
save_trade(trade)

View File

@ -1,2 +0,0 @@
7af1b1d333431217958032adc11b84278939d400a7368d33bab4e8455b4203f0
7997c2eafeda0815b840bfbd60e5ad70d8879aac252bd594c732a04d6a7b85e9

View File

@ -1,4 +0,0 @@
#!/bin/bash
# /home/jay/Zcash/python3-xcat/protocol/checktx.py $@
echo "$@" >> "/home/jay/Zcash/python3-xcat/protocol/watchdata"

View File

@ -1 +1 @@
{"id": 1, "buy": {"initiator": "tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ", "fund_tx": "9df973f468ce0b4398a9db2e8710b0ec268a3b3118f996f67f808af97e53a161", "fulfiller": "tmTjZSg4pX2Us6V5HttiwFZwj464fD2ZgpY", "p2sh": "t2QrLFUqmp1v1xQSE3hmgwcYuinRb3BRWMm", "amount": 1.2, "currency": "zcash"}, "sell": {"status": "funded", "fund_tx": "d1f679a3ee51fd563224e8114bed1d79fa6dc54d48163ec12ba17ba1d9e76baa", "p2sh": "2MuWU5BpLpqJvvzkCPq8gFHA4VFFGyvjaJf", "fulfiller": "mrQzUGU1dwsWRx5gsKKSDPNtrsP65vCA3Z", "initiator": "myfFr5twPYNwgeXyjCmGcrzXtCmfmWXKYp", "amount": 3.5, "currency": "bitcoin", "secret": "rabbits"}}
{"sell": {"currency": "bitcoin", "fulfiller": "mrQzUGU1dwsWRx5gsKKSDPNtrsP65vCA3Z", "initiator": "myfFr5twPYNwgeXyjCmGcrzXtCmfmWXKYp", "amount": "0.5", "redeemScript": "63a82033fb8f68c7e079a2a35cfcd8827279f8a55fa1be04c99debd8ed3e54954e08228876a9147788b4511a25fba1092e67b307a6dcdb6da125d96702bd01b17576a914c7043e62a7391596116f54f6a64c8548e97d3fd96888ac", "fund_tx": "6cca31678d7fe6461277cea7e0614844e62ccc3ae4e247ca77d62bdd741fabef", "p2sh": "2N3rA4r6VSx65FeBTcgWWcR9HaW69DYDbrp", "redeemblocknum": 445}, "buy": {"currency": "zcash", "fulfiller": "tmTjZSg4pX2Us6V5HttiwFZwj464fD2ZgpY", "initiator": "tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ", "amount": "1.12", "refund_tx": "5e34f0c634fb6207e9c85ed52528629404d25db29b68931b4e0278377ee776d6", "redeemScript": "63a82033fb8f68c7e079a2a35cfcd8827279f8a55fa1be04c99debd8ed3e54954e08228876a9143ea29256c9d2888ca23de42a8b8e69ca2ec235b16702e301b17576a914c5acca6ef39c843c7a9c3ad01b2da95fe2edf5ba6888ac", "fund_tx": "8c9f9581a8cab00836b54237b30a6d68125067e3ffe6ae04413da2bdc9146daa", "p2sh": "t2UkYvcnigZt7FFp86UvTZ78qdGeLXw7tet", "redeemblocknum": 483}}

340
xcat.py
View File

@ -6,6 +6,8 @@ from time import sleep
import json
import os, sys
from pprint import pprint
from trades import Contract, Trade
import userInput
def check_p2sh(currency, address):
if currency == 'bitcoin':
@ -15,24 +17,6 @@ def check_p2sh(currency, address):
print("Checking funds in Zcash p2sh")
return zXcat.check_funds(address)
def set_price():
trade = {}
#TODO: make currencies interchangeable. Save to a tuple?
sell = input("Which currency would you like to trade out of? (bitcoin)")
sell = 'bitcoin'
buy = 'zcash'
sell_amt = input("How much {0} do you want to sell?".format(sell))
sell_amt = 3.5
print(sell_amt)
buy_amt = input("How much {0} do you want to receive in exchange?".format(buy))
buy_amt = 1.2
print(buy_amt)
sell = {'currency': sell, 'amount': sell_amt}
buy = {'currency': buy, 'amount': buy_amt}
trade['sell'] = sell
trade['buy'] = buy
save_trade(trade)
def create_htlc(currency, funder, redeemer, secret, locktime):
if currency == 'bitcoin':
sell_p2sh = bXcat.hashtimelockcontract(funder, redeemer, secret, locktime)
@ -47,177 +31,166 @@ def fund_htlc(currency, p2sh, amount):
txid = zXcat.fund_htlc(p2sh, amount)
return txid
def initiate_trade():
trade = get_trade()
currency = trade['sell']['currency']
secret = input("Initiating trade: Enter a password to place the {0} you want to sell in escrow: ".format(currency))
# TODO: hash and store secret only locally.
# secret = 'test'
print('Remember your password:', secret)
locktime = 20 # Must be more than first tx
def fund_buy_contract(trade):
buy = trade.buyContract
txid = fund_htlc(buy.currency, buy.p2sh, buy.amount)
setattr(trade.buyContract, 'fund_tx', txid)
save(trade)
return txid
# Returns contract obj
contracts = {}
contract = create_htlc(currency, trade['sell']['initiator'], trade['sell']['fulfiller'], secret, locktime)
sell_p2sh = contract['p2sh']
contracts[contract['p2sh']] = contract
save_contract(contracts)
def fund_sell_contract(trade):
sell = trade.sellContract
txid = fund_htlc(sell.currency, sell.p2sh, sell.amount)
setattr(trade.sellContract, 'fund_tx', txid)
save(trade)
return txid
print('To complete your sell, send {0} {1} to this p2sh: {2}'.format(trade['sell']['amount'], currency, contract['p2sh']))
response = input("Type 'enter' to allow this program to send funds on your behalf.")
print("Sent")
def create_sell_p2sh(trade, secret, locktime):
# CREATE SELL CONTRACT
sell = trade.sellContract
contract = create_htlc(sell.currency, sell.initiator, sell.fulfiller, secret, locktime)
print("sell contract", contract)
setattr(trade.sellContract, 'p2sh', contract['p2sh'])
setattr(trade.sellContract, 'redeemScript', contract['redeemScript'])
setattr(trade.sellContract, 'redeemblocknum', contract['redeemblocknum'])
save(trade)
sell_amt = trade['sell']['amount']
txid = fund_htlc(currency, sell_p2sh, sell_amt)
def create_buy_p2sh(trade, secret, locktime):
## CREATE BUY CONTRACT
buy = trade.buyContract
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)
buy_contract = create_htlc(buy.currency, buy.fulfiller, buy.initiator, secret, locktime)
print("Buy contract", buy_contract)
trade['sell']['p2sh'] = sell_p2sh
trade['sell']['fund_tx'] = txid
trade['sell']['status'] = 'funded'
# TODO: Save secret locally for seller
trade['sell']['secret'] = secret
setattr(trade.buyContract, 'p2sh', buy_contract['p2sh'])
setattr(trade.buyContract, 'redeemScript', buy_contract['redeemScript'])
setattr(trade.buyContract, 'redeemblocknum', buy_contract['redeemblocknum'])
print("Now contact the buyer and tell them to send funds to this p2sh: ", trade.buyContract.p2sh)
save_trade(trade)
save(trade)
buy_currency = trade['buy']['currency']
buy_initiator = trade['buy']['initiator']
buy_fulfiller = trade['buy']['fulfiller']
print("Now creating buy contract on the {0} blockchain where you will wait for the buyer to send funds...".format(buy_currency))
buy_contract = create_htlc(buy_currency, buy_fulfiller, buy_initiator, secret, locktime)
buy_p2sh = buy_contract['p2sh']
contracts[buy_contract['p2sh']] = buy_contract
save_contract(contracts)
print("Now contact the buyer and tell them to send funds to this p2sh: ", buy_p2sh)
trade['buy']['p2sh'] = buy_p2sh
save_trade(trade)
def get_addresses():
trade = get_trade()
sell = trade['sell']['currency']
buy = trade['buy']['currency']
init_offer_addr = input("Enter your {0} address: ".format(sell))
# init_offer_addr = bXcat.new_bitcoin_addr()
init_offer_addr = 'myfFr5twPYNwgeXyjCmGcrzXtCmfmWXKYp'
print(init_offer_addr)
init_bid_addr = input("Enter your {0} address: ".format(buy))
# init_bid_addr = zXcat.new_zcash_addr()
init_bid_addr = 'tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ'
print(init_bid_addr)
trade['sell']['initiator'] = init_offer_addr
trade['buy']['initiator'] = init_bid_addr
fulfill_offer_addr = input("Enter the {0} address of the party you want to trade with: ".format(sell))
# fulfill_offer_addr = bXcat.new_bitcoin_addr()
fulfill_offer_addr = 'mrQzUGU1dwsWRx5gsKKSDPNtrsP65vCA3Z'
print(fulfill_offer_addr)
fulfill_bid_addr = input("Enter the {0} address of the party you want to trade with: ".format(buy))
# fulfill_bid_addr = zXcat.new_zcash_addr()
fulfill_bid_addr = 'tmTjZSg4pX2Us6V5HttiwFZwj464fD2ZgpY'
print(fulfill_bid_addr)
trade['sell']['fulfiller'] = fulfill_offer_addr
trade['buy']['fulfiller'] = fulfill_bid_addr
# zec_funder, zec_redeemer = zXcat.get_keys(zec_fund_addr, zec_redeem_addr)
trade['id'] = 1
save_trade(trade)
def buyer_fulfill():
trade = get_trade()
buy_p2sh = trade['buy']['p2sh']
sell_p2sh = trade['sell']['p2sh']
buy_amount = check_p2sh(trade['buy']['currency'], buy_p2sh)
sell_amount = check_p2sh(trade['sell']['currency'], sell_p2sh)
amount = trade['buy']['amount']
currency = trade['buy']['currency']
if buy_amount == 0:
input("The seller's p2sh is funded with {0} {1}, type 'enter' if this is the amount you want to buy in {1}.".format(trade['sell']['amount'], trade['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(amount, currency))
p2sh = trade['buy']['p2sh']
txid = fund_htlc(currency, p2sh, amount)
trade['buy']['fund_tx'] = txid
save_trade(trade)
else:
print("It looks like you've already funded the contract to buy {1}, the amount in escrow in the p2sh is {0}.".format(amount, currency))
print("Please wait for the seller to remove your funds from escrow to complete the trade.")
def check_blocks(p2sh):
# blocks = []
with open('watchdata', 'r') as infile:
for line in infile:
res = bXcat.search_p2sh(line.strip('\n'), p2sh)
# blocks.append(line.strip('\n'))
# print(blocks)
# for block in blocks:
# res = bXcat.search_p2sh(block, p2sh)
def redeem_p2sh(currency, p2sh, action):
# action is buy or sell
def redeem_p2sh(contract, secret):
currency = contract.currency
if currency == 'bitcoin':
res = bXcat.redeem(p2sh, action)
res = bXcat.auto_redeem(contract, secret)
else:
res = zXcat.redeem(p2sh, action)
res = zXcat.auto_redeem(contract, secret)
return res
def seller_redeem():
# add locktime as variable?
def print_trade(role):
print("\nTrade status for {0}:".format(role))
trade = get_trade()
if 'status' in trade['buy'] and trade['buy']['status'] == 'redeemed':
print("You already redeemed the funds and acquired {0} {1}".format(trade['buy']['amount'], trade['buy']['currency']))
exit()
else:
# Seller redeems buyer's funded tx (contract in p2sh)
p2sh = trade['buy']['p2sh']
currency = trade['buy']['currency']
redeem_tx = redeem_p2sh(currency, p2sh, 'buy')
trade['buy']['redeem_tx'] = redeem_tx
trade['buy']['status'] = 'redeemed'
save_trade(trade)
pprint(trade)
def buyer_redeem():
trade = get_trade()
if 'status' in trade['sell'] and trade['sell']['status'] == 'redeemed':
print("You already redeemed the funds and acquired {0} {1}".format(trade['sell']['amount'], trade['sell']['currency']))
#### Main functions determining user flow from command line
def buyer_redeem(trade):
userInput.authorize_buyer_redeem(trade)
if trade.sellContract.get_status() == 'redeemed':
print("You already redeemed the funds and acquired {0} {1}".format(trade.sellContract.amount, trade.sellContract.currency))
exit()
else:
# Buyer redeems seller's funded tx
p2sh = trade['sell']['p2sh']
currency = trade['sell']['currency']
redeem_tx = redeem_p2sh(currency, p2sh, 'sell')
trade['sell']['redeem_tx'] = redeem_tx
trade['sell']['status'] = 'redeemed'
save_trade(trade)
p2sh = trade.sellContract.p2sh
currency = trade.sellContract.currency
# Buy contract is where seller disclosed secret in redeeming
if trade.buyContract.currency == 'bitcoin':
secret = bXcat.parse_secret(trade.buyContract.redeem_tx)
else:
secret = zXcat.parse_secret(trade.buyContract.redeem_tx)
print("Found secret in seller's redeem tx", secret)
redeem_tx = redeem_p2sh(trade.sellContract, secret)
setattr(trade.sellContract, 'redeem_tx', redeem_tx)
save(trade)
exit()
def print_trade(role):
print("Trade status:")
trade = get_trade()
if role == 'seller':
pprint(trade)
def seller_redeem(trade):
buy = trade.buyContract
userInput.authorize_seller_redeem(buy)
if trade.sellContract.get_status() == 'redeemed':
print("You already redeemed the funds and acquired {0} {1}".format(buy.amount, buy.currency))
exit()
else:
del trade['sell']['secret']
pprint(trade)
# Seller redeems buyer's funded tx (contract in p2sh)
secret = userInput.retrieve_password()
tx_type, txid = redeem_p2sh(trade.buyContract, secret)
setattr(trade.buyContract, tx_type, txid)
save(trade)
print("You have redeemed {0} {1}!".format(buy.amount, buy.currency))
print_trade('seller')
def buyer_fulfill(trade):
buy = trade.buyContract
sell = trade.sellContract
buy_p2sh_balance = check_p2sh(buy.currency, buy.p2sh)
sell_p2sh_balance = check_p2sh(sell.currency, sell.p2sh)
if buy_p2sh_balance == 0:
userInput.authorize_buyer_fulfill(sell_p2sh_balance, sell.currency, buy_p2sh_balance, buy.currency)
print("Buy amt:", buy.amount)
txid = fund_buy_contract(trade)
print("Fund tx txid:", txid)
else:
print("It looks like you've already funded the contract to buy {1}, the amount in escrow in the p2sh is {0}.".format(buy_p2sh_balance, buy.currency))
print("Please wait for the seller to remove your funds from escrow to complete the trade.")
print_trade('buyer')
def seller_initiate(trade):
# Get amounts
amounts = userInput.get_trade_amounts()
sell = amounts['sell']
buy = amounts['buy']
sell_currency = sell['currency']
buy_currency = buy['currency']
# Get addresses
init_addrs = userInput.get_initiator_addresses()
sell['initiator'] = init_addrs[sell_currency]
buy['initiator'] = init_addrs[buy_currency]
fulfill_addrs = userInput.get_fulfiller_addresses()
sell['fulfiller'] = fulfill_addrs[sell_currency]
buy['fulfiller'] = fulfill_addrs[buy_currency]
# initializing contract classes with addresses, currencies, and amounts
trade.sellContract = Contract(sell)
trade.buyContract = Contract(buy)
print(trade.sellContract.__dict__)
print(trade.buyContract.__dict__)
secret = userInput.create_password()
# TODO: Implement locktimes and mock block passage of time
sell_locktime = 5
buy_locktime = 10 # Must be more than first tx
create_sell_p2sh(trade, secret, sell_locktime)
userInput.authorize_fund_sell(trade)
txid = fund_sell_contract(trade)
print("Sent")
create_buy_p2sh(trade, secret, buy_locktime)
print_trade('seller')
if __name__ == '__main__':
print("ZEC <-> BTC XCAT (Cross-Chain Atomic Transactions)")
# TODO: Get trade indicated by id number
# TODO: pass trade into functions?
# TODO: workflow framed as currency you're trading out of being sell. appropriate?
# Have initiator propose amounts to trade
print("=" * 50)
trade = get_trade()
if trade == None:
htlcTrade = Trade()
print("New empty trade")
else:
buyContract = Contract(trade['buy'])
sellContract = Contract(trade['sell'])
htlcTrade = Trade(buyContract=buyContract, sellContract=sellContract)
try:
if sys.argv[1] == 'new':
erase_trade()
role = 'seller'
trade = get_trade()
htlcTrade = Trade()
print("Creating new XCAT transaction...")
else:
role = sys.argv[1]
print("Your role in demo:", role)
@ -233,46 +206,31 @@ if __name__ == '__main__':
print("Trade exists, run script as buyer or seller to complete trade.")
exit()
if trade is not None:
if trade['sell']['status'] == 'redeemed' and trade['buy']['status'] == 'redeemed':
if htlcTrade.buyContract is not None and htlcTrade.sellContract is not None:
if htlcTrade.sellContract.get_status() == 'redeemed' and htlcTrade.buyContract.get_status() == 'redeemed':
print("This trade is already complete! Trade details:")
pprint(trade)
exit()
if role == "seller":
if trade == None or 'status' not in trade['sell']:
set_price()
get_addresses()
initiate_trade()
print_trade('seller')
elif 'status' in trade['sell']:
if 'fund_tx' in trade['buy']:
# Means buyer has already funded the currency the transaction initiator wants to exchange into
print("Buyer funded the contract where you offered to buy {0}, redeeming funds from {1}...".format(trade['buy']['currency'], trade['buy']['p2sh']))
seller_redeem()
print("You have redeemed {0} {1}!".format(trade['buy']['amount'], trade['buy']['currency']))
print_trade('seller')
else:
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_trade('seller')
if htlcTrade.sellContract == None:
seller_initiate(htlcTrade)
elif htlcTrade.buyContract.get_status() == 'funded':
seller_redeem(htlcTrade)
elif htlcTrade.buyContract.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.buyContract.currency))
else:
# Need better way of preventing buyer from having secret
if 'status' not in trade['buy'] and trade['sell']['status'] == 'funded':
# if 'status' not in trade['buy'] and trade['sell']['status'] == 'funded':
if htlcTrade.sellContract.get_status() == 'funded' and htlcTrade.buyContract.get_status() != 'redeemed':
print("One active trade available, fulfilling buyer contract...")
trade = get_trade()
buyer_fulfill()
buyer_fulfill(htlcTrade)
# How to monitor if txs are included in blocks -- should use blocknotify and a monitor daemon?
# For regtest, can mock in a function
# p2sh = trade['buy']['p2sh']
# check_blocks(p2sh)
print_trade('buyer')
elif trade['buy']['status'] == 'redeemed':
elif htlcTrade.buyContract.get_status() == 'redeemed':
# Seller has redeemed buyer's tx, buyer can now redeem.
print("The seller has redeemed the contract where you paid them in {0}, now redeeming your funds from {1}".format(trade['buy']['currency'], trade['sell']['p2sh']))
buyer_redeem()
buyer_redeem(htlcTrade)
print("XCAT trade complete!")
print_trade('buyer')
# Note: there is some little endian weirdness in the bXcat and zXcat files, need to handle the endianness of txids better & more consistently

210
zXcat.py
View File

@ -14,7 +14,7 @@ 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
from zcash.wallet import CBitcoinAddress, CBitcoinSecret, P2SHBitcoinAddress, P2PKHBitcoinAddress
from utils import *
@ -39,7 +39,9 @@ def hashtimelockcontract(funder, redeemer, secret, locktime):
redeemerAddr = CBitcoinAddress(redeemer)
h = sha256(secret)
blocknum = zcashd.getblockcount()
print("Current blocknum", blocknum)
redeemblocknum = blocknum + locktime
print("REDEEMBLOCKNUM ZCASH", redeemblocknum)
zec_redeemScript = CScript([OP_IF, OP_SHA256, h, 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])
@ -49,10 +51,10 @@ def hashtimelockcontract(funder, redeemer, secret, locktime):
txin_p2sh_address = CBitcoinAddress.from_scriptPubKey(txin_scriptPubKey)
p2sh = str(txin_p2sh_address)
# Returning all this to be saved locally in p2sh.json
return {'p2sh': p2sh, 'redeemblocknum': redeemblocknum, 'zec_redeemScript': b2x(zec_redeemScript), 'redeemer': redeemer, 'funder': funder}
return {'p2sh': p2sh, 'redeemblocknum': redeemblocknum, 'redeemScript': b2x(zec_redeemScript), 'redeemer': redeemer, 'funder': funder}
def fund_htlc(p2sh, amount):
send_amount = amount*COIN
send_amount = float(amount)*COIN
fund_txid = zcashd.sendtoaddress(p2sh, send_amount)
txid = b2x(lx(b2x(fund_txid)))
return txid
@ -70,68 +72,168 @@ def get_tx_details(txid):
fund_txinfo = zcashd.gettransaction(txid)
return fund_txinfo['details'][0]
def redeem(p2sh, action):
contracts = get_contract()
trade = get_trade()
for key in contracts:
if key == p2sh:
contract = contracts[key]
if contract:
print("Redeeming tx in p2sh", p2sh)
# TODO: Have to get tx info from saved contract p2sh
redeemblocknum = contract['redeemblocknum']
zec_redeemScript = contract['zec_redeemScript']
def find_transaction_to_address(p2sh):
zcashd.importaddress(p2sh, "", False)
txs = zcashd.listunspent()
for tx in txs:
# print("tx addr:", tx['address'])
# print(type(tx['address']))
# print(type(p2sh))
if tx['address'] == CBitcoinAddress(p2sh):
print("Found tx to p2sh", p2sh)
print(tx)
return tx
txid = trade[action]['fund_tx']
details = get_tx_details(txid)
print("Txid for fund tx", txid)
# must be little endian hex
txin = CMutableTxIn(COutPoint(lx(txid), details['vout']))
redeemPubKey = CBitcoinAddress(contract['redeemer'])
amount = trade[action]['amount'] * COIN
print("amount: {0}, fee: {1}".format(amount, FEE))
txout = CMutableTxOut(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...
# Is stored as hex, must convert to bytes
zec_redeemScript = CScript(x(zec_redeemScript))
# 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]
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])
def find_secret(p2sh):
return parse_secret('4c25b5db9f3df48e48306891d8437c69308afa122f92416df1a3ba0d3604882f')
zcashd.importaddress(p2sh, "", False)
# is this working?
txs = zcashd.listtransactions()
for tx in txs:
# print("tx addr:", tx['address'])
# print(type(tx['address']))
# print(type(p2sh))
if (tx['address'] == p2sh ) and (tx['category'] == "send"):
print(type(tx['txid']))
print(str.encode(tx['txid']))
raw = zcashd.getrawtransaction(lx(tx['txid']),True)['hex']
decoded = zcashd.decoderawtransaction(raw)
print("deo:", decoded['vin'][0]['scriptSig']['asm'])
# TODO: Figure out where to store secret preimage securely. Parse from scriptsig of redeemtx
secret = trade['sell']['secret']
preimage = secret.encode('utf-8')
print('preimage', preimage)
def parse_secret(txid):
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]
secret = hex2str(asm[2])
redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey))
print('redeemPubkey', redeemPubkey)
print(secret)
return secret
# print('zec_redeemScript', zec_redeemScript)
txin.scriptSig = CScript([sig, privkey.pub, preimage, OP_TRUE, zec_redeemScript])
# print("Redeem tx hex:", b2x(tx.serialize()))
# redeems automatically after buyer has funded tx, by scanning for transaction to the p2sh
# i.e., doesn't require buyer telling us fund txid
# Can only call to_p2sh_scriptPubKey on CScript obj
txin_scriptPubKey = zec_redeemScript.to_p2sh_scriptPubKey()
def auto_redeem(contract, 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
print("Found fundtx:", fundtx)
p2sh = P2SHBitcoinAddress(p2sh)
if fundtx['address'] == p2sh:
print("Found {0} in p2sh {1}, redeeming...".format(amount, p2sh))
# print("txin.scriptSig", b2x(txin.scriptSig))
# print("txin_scriptPubKey", b2x(txin_scriptPubKey))
# print('tx', tx)
VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,))
print("Script verified, sending raw tx...")
print("Raw tx of prepared redeem tx: ", b2x(tx.serialize()))
txid = zcashd.sendrawtransaction(tx)
txhex = b2x(lx(b2x(txid)))
print("Txid of submitted redeem tx: ", txhex)
return txhex
# 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 < redeemblocknum:
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:
# if blockcount >= redeemblocknum:
# tx.nLockTime = redeemblocknum
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):
redeemScript = zcashd.decodescript(script_hex)
scriptarray = redeemScript['asm'].split(' ')
return scriptarray
def find_redeemblocknum(contract):
scriptarray = parse_script(contract.redeemScript)
redeemblocknum = scriptarray[8]
return int(redeemblocknum)
def find_redeemAddr(contract):
scriptarray = parse_script(contract.redeemScript)
redeemer = scriptarray[6]
redeemAddr = P2PKHBitcoinAddress.from_bytes(x(redeemer))
return redeemAddr
def find_refundAddr(contract):
scriptarray = parse_script(contract.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.to_p2sh_scriptPubKey)
return addr.to_scriptPubKey()
def generate(num):
blocks = zcashd.generate(num)
return blocks

View File

@ -16,6 +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
import hashlib
import binascii
# SelectParams('testnet')
SelectParams('regtest')