commit
6fec17cd81
|
@ -1 +1,2 @@
|
|||
*.pyc
|
||||
xcatdb/
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
if sys.version_info.major < 3:
|
||||
sys.stderr.write('Sorry, Python 3.x required by this example.\n')
|
||||
sys.exit(1)
|
||||
|
||||
import bitcoin
|
||||
import bitcoin.rpc
|
||||
from bitcoin import SelectParams
|
||||
from bitcoin.core import b2x, lx, b2lx, x, COIN, COutPoint, CMutableTxOut, CMutableTxIn, CMutableTransaction, Hash160, CTransaction
|
||||
from bitcoin.base58 import decode
|
||||
from bitcoin.core.script import CScript, OP_DUP, OP_IF, OP_ELSE, OP_ENDIF, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG, SignatureHash, SIGHASH_ALL, OP_FALSE, OP_DROP, OP_CHECKLOCKTIMEVERIFY, OP_SHA256, OP_TRUE
|
||||
from bitcoin.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH
|
||||
from bitcoin.wallet import CBitcoinAddress, CBitcoinSecret
|
||||
|
||||
from utils import *
|
||||
|
||||
import zcash
|
||||
import zcash.rpc
|
||||
import pprint, json
|
||||
|
||||
# SelectParams('testnet')
|
||||
SelectParams('regtest')
|
||||
bitcoind = bitcoin.rpc.Proxy()
|
||||
FEE = 0.001*COIN
|
||||
|
||||
zcashd = zcash.rpc.Proxy()
|
||||
|
||||
def get_keys(funder_address, redeemer_address):
|
||||
fundpubkey = CBitcoinAddress(funder_address)
|
||||
redeempubkey = CBitcoinAddress(redeemer_address)
|
||||
# fundpubkey = bitcoind.getnewaddress()
|
||||
# redeempubkey = bitcoind.getnewaddress()
|
||||
return fundpubkey, redeempubkey
|
||||
|
||||
def privkey(address):
|
||||
bitcoind.dumpprivkey(address)
|
||||
|
||||
def hashtimelockcontract(funder, redeemer, secret, locktime):
|
||||
funderAddr = CBitcoinAddress(funder)
|
||||
redeemerAddr = CBitcoinAddress(redeemer)
|
||||
h = sha256(secret)
|
||||
blocknum = bitcoind.getblockcount()
|
||||
redeemblocknum = blocknum + locktime
|
||||
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])
|
||||
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)
|
||||
return {'p2sh': p2sh, 'redeemblocknum': redeemblocknum, 'zec_redeemScript': b2x(zec_redeemScript), 'redeemer': redeemer, 'funder': funder}
|
||||
|
||||
def fund_htlc(p2sh, amount):
|
||||
send_amount = amount*COIN
|
||||
fund_txid = bitcoind.sendtoaddress(p2sh, send_amount)
|
||||
txid = b2x(lx(b2x(fund_txid)))
|
||||
return txid
|
||||
|
||||
def check_funds(p2sh):
|
||||
bitcoind.importaddress(p2sh, "", False)
|
||||
# Get amount in address
|
||||
amount = bitcoind.getreceivedbyaddress(p2sh, 0)
|
||||
amount = amount/COIN
|
||||
return amount
|
||||
|
||||
## TODO: FIX search for p2sh in block
|
||||
def search_p2sh(block, p2sh):
|
||||
print("Fetching block...")
|
||||
blockdata = bitcoind.getblock(lx(block))
|
||||
print("done fetching block")
|
||||
txs = blockdata.vtx
|
||||
print("txs", txs)
|
||||
for tx in txs:
|
||||
txhex = b2x(tx.serialize())
|
||||
# Using my fork of python-zcashlib to get result of decoderawtransaction
|
||||
txhex = txhex + '00'
|
||||
rawtx = zcashd.decoderawtransaction(txhex)
|
||||
# print('rawtx', rawtx)
|
||||
print(rawtx)
|
||||
for vout in rawtx['vout']:
|
||||
if 'addresses' in vout['scriptPubKey']:
|
||||
for addr in vout['scriptPubKey']['addresses']:
|
||||
print("Sent to address:", addr)
|
||||
if addr == 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)
|
||||
|
||||
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']
|
||||
|
||||
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))
|
||||
|
||||
tx.nLockTime = redeemblocknum
|
||||
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])
|
||||
# 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)
|
||||
|
||||
# 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
|
||||
txin_scriptPubKey = zec_redeemScript.to_p2sh_scriptPubKey()
|
||||
|
||||
# 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 = bitcoind.sendrawtransaction(tx)
|
||||
txhex = b2x(lx(b2x(txid)))
|
||||
print("Txid of submitted redeem tx: ", txhex)
|
||||
return txhex
|
||||
else:
|
||||
print("No contract for this p2sh found in database", p2sh)
|
||||
|
||||
def new_bitcoin_addr():
|
||||
addr = bitcoind.getnewaddress()
|
||||
print('new btc addr', addr.to_scriptPubKey)
|
||||
return addr.to_scriptPubKey()
|
|
@ -0,0 +1,138 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Based on spend-p2sh-txout.py from python-bitcoinlib.
|
||||
# Copyright (C) 2017 The Zcash developers
|
||||
|
||||
import sys
|
||||
if sys.version_info.major < 3:
|
||||
sys.stderr.write('Sorry, Python 3.x required by this example.\n')
|
||||
sys.exit(1)
|
||||
|
||||
import bitcoin
|
||||
import bitcoin.rpc
|
||||
from bitcoin import SelectParams
|
||||
from bitcoin.core import b2x, lx, b2lx, COIN, COutPoint, CMutableTxOut, CMutableTxIn, CMutableTransaction, Hash160
|
||||
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
|
||||
import hashlib
|
||||
|
||||
# SelectParams('testnet')
|
||||
SelectParams('regtest')
|
||||
bitcoind = bitcoin.rpc.Proxy()
|
||||
FEE = 0.001*COIN
|
||||
|
||||
# ========================= BITCOIN ADDRESSES =========================
|
||||
alice_address = input("Enter alice bitcoin address: (type 'enter' for demo)")
|
||||
bob_address = input("Enter bob bitcoin address: (type 'enter' for demo)")
|
||||
# alicepubkey = CBitcoinAddress('mshp4msfzc73ebg4VzwS6nAXj9t6KqX1wd')
|
||||
# bobpubkey = CBitcoinAddress('myRh2T5Kg7QJfGLeRzriT5zs9aoek5Jbha')
|
||||
# bitcoind.getnewaddress() returns CBitcoinAddress
|
||||
bobpubkey = bitcoind.getnewaddress()
|
||||
alicepubkey = bitcoind.getnewaddress()
|
||||
print("alicepubkey", alicepubkey)
|
||||
print("bobpubkey", bobpubkey)
|
||||
# privkey of the bob, used to sign the redeemTx
|
||||
bob_seckey = bitcoind.dumpprivkey(bobpubkey)
|
||||
# privkey of alice, used to refund tx in case of timeout
|
||||
alice_seckey = bitcoind.dumpprivkey(alicepubkey)
|
||||
|
||||
# ========================= HASHLOCK SECRET PREIMAGE =========================
|
||||
secret = input("Alice: Enter secret to lock funds: (type 'enter' for demo)")
|
||||
# preimage = secret.encode('UTF-8')
|
||||
preimage = b'preimage'
|
||||
h = hashlib.sha256(preimage).digest()
|
||||
|
||||
# ========================= LOCKTIME SCRIPT CREATION =========================
|
||||
lockduration = 10
|
||||
blocknum = bitcoind.getblockcount()
|
||||
redeemblocknum = blocknum + lockduration
|
||||
# Create a htlc redeemScript. Similar to a scriptPubKey the redeemScript must be
|
||||
# satisfied for the funds to be spent.
|
||||
btc_redeemScript = CScript([OP_IF, OP_SHA256, h, OP_EQUALVERIFY,OP_DUP, OP_HASH160,
|
||||
alicepubkey, OP_ELSE, redeemblocknum, OP_CHECKLOCKTIMEVERIFY, OP_DROP, OP_DUP, OP_HASH160,
|
||||
bobpubkey, OP_ENDIF,OP_EQUALVERIFY, OP_CHECKSIG])
|
||||
print("Redeem script:", b2x(btc_redeemScript))
|
||||
|
||||
# ========================= TX1: CREATE BITCOIN P2SH FROM SCRIPT =========================
|
||||
txin_scriptPubKey = btc_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)
|
||||
print('Bob -- Assuming Alice has created other tx on Zcash blockchain, send funds to this p2sh address:', p2sh)
|
||||
|
||||
## TODO: IMPORT ZCASH XCAT FUNCTIONS
|
||||
|
||||
|
||||
# ========================= FUND BITCOIN P2SH =========================
|
||||
response = input("Bob -- Type 'enter' to allow zbxcat to fund the Bitcoin p2sh on your behalf:")
|
||||
send_amount = 1.0*COIN
|
||||
fund_tx = bitcoind.sendtoaddress(txin_p2sh_address, send_amount)
|
||||
print('Alice -- Bitcoin fund tx was sent, please wait for confirmation. Txid:', b2x(lx(b2x(fund_tx))))
|
||||
|
||||
# ========================= PART 2: BITCOIN P2SH FUNDED, REDEEM OR REFUND =========================
|
||||
# Check that fund_tx is on the blockchain to the right address, then notify receiver
|
||||
# Importing address so we can watch it
|
||||
bitcoind.importaddress(p2sh)
|
||||
# Get details of funding transaction
|
||||
fund_txinfo = bitcoind.gettransaction(fund_tx)
|
||||
fund_details = fund_txinfo['details'] # "fund_details" is an array, for now we can assume it only has one destination address
|
||||
outputAddress = fund_details[0]['address']
|
||||
fund_vout = fund_details[0]['vout']
|
||||
if (outputAddress != p2sh):
|
||||
print('Fund tx sent to wrong address! p2sh was {0}, funding tx was sent to {1}'.format(p2sh, outputAddress))
|
||||
quit()
|
||||
# Check amount by inspecting imported address
|
||||
output_amount = bitcoind.getreceivedbyaddress(outputAddress, 0)
|
||||
if (output_amount < send_amount):
|
||||
print('Fund tx too small! Amount sent was {0}, amount expected was {1}'.format(output_amount, send_amount))
|
||||
quit()
|
||||
print("P2SH {0} successfully funded with {1}".format(p2sh, send_amount))
|
||||
|
||||
print('Alice -- the fund tx has been confirmed, now you can redeem your Bitcoin with the secret!')
|
||||
|
||||
|
||||
# ========================= CHECKLOCKIME FOR BITCOIN TX1 =========================
|
||||
# Mock the timeout period passing for tx1 (comment this out to proceed to redeemtx)
|
||||
# bitcoind.generate(20)
|
||||
|
||||
# ========================= BITCOIN REFUND CONDITION =========================
|
||||
# AFTER 24 HRS (by blocknum): If locktime for first tx has passed, tx1 is refunded to alice
|
||||
if(bitcoind.getblockcount() >= redeemblocknum):
|
||||
print("Bob -- Alice did not redeem within the timeout period, so refunding your bitcoin....... ")
|
||||
txin = CMutableTxIn(COutPoint(fund_tx, fund_vout))
|
||||
# The default nSequence of FFFFFFFF won't let you redeem when there's a CHECKTIMELOCKVERIFY
|
||||
txin.nSequence = 0
|
||||
txout = CMutableTxOut(send_amount - FEE, bobpubkey.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
|
||||
tx.nLockTime = redeemblocknum
|
||||
# Calculate the signature hash for that transaction. Note how the script we use
|
||||
# is the redeemScript, not the scriptPubKey. EvalScript() will be evaluating the redeemScript
|
||||
sighash = SignatureHash(btc_redeemScript, tx, 0, SIGHASH_ALL)
|
||||
sig = bob_seckey.sign(sighash) + bytes([SIGHASH_ALL])
|
||||
txin.scriptSig = CScript([sig, bob_seckey.pub, OP_FALSE, btc_redeemScript])
|
||||
print("Time lock has passed, Bob redeeming his own tx:")
|
||||
print("Refund tx hex:", b2x(tx.serialize()))
|
||||
VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,))
|
||||
txid = bitcoind.sendrawtransaction(tx)
|
||||
print("Txid of submitted refund tx: ", b2x(lx(b2x(txid))))
|
||||
quit()
|
||||
|
||||
# ========================= BITCOIN REDEEM CONDITION =========================
|
||||
# BEFORE 24 HRS (by blocknum): Alice redeems bitcoin tx bob funded
|
||||
print("Alice -- Redeeming tx.....")
|
||||
txin = CMutableTxIn(COutPoint(fund_tx, fund_vout))
|
||||
txout = CMutableTxOut(send_amount - FEE, alicepubkey.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
|
||||
tx.nLockTime = redeemblocknum
|
||||
sighash = SignatureHash(btc_redeemScript, tx, 0, SIGHASH_ALL)
|
||||
sig = alice_seckey.sign(sighash) + bytes([SIGHASH_ALL])
|
||||
txin.scriptSig = CScript([sig, alice_seckey.pub, preimage, OP_TRUE, btc_redeemScript])
|
||||
print("Redeem tx hex:", b2x(tx.serialize()))
|
||||
VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,))
|
||||
txid = bitcoind.sendrawtransaction(tx)
|
||||
print("Txid of submitted redeem tx: ", b2x(lx(b2x(txid))))
|
|
@ -11,7 +11,7 @@ if sys.version_info.major < 3:
|
|||
import bitcoin
|
||||
import bitcoin.rpc
|
||||
from bitcoin import SelectParams
|
||||
from bitcoin.core import b2x, lx, COIN, COutPoint, CMutableTxOut, CMutableTxIn, CMutableTransaction, Hash160
|
||||
from bitcoin.core import b2x, lx, b2lx, COIN, COutPoint, CMutableTxOut, CMutableTxIn, CMutableTransaction, Hash160
|
||||
from bitcoin.core.script import CScript, OP_DUP, OP_IF, OP_ELSE, OP_ENDIF, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG, SignatureHash, SIGHASH_ALL
|
||||
from bitcoin.core.script import OP_DROP, OP_CHECKLOCKTIMEVERIFY, OP_SHA256, OP_TRUE
|
||||
from bitcoin.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH
|
||||
|
@ -34,12 +34,15 @@ senderpubkey = proxy.getnewaddress()
|
|||
# privkey of the recipient, used to sign the redeemTx
|
||||
seckey = proxy.dumpprivkey(recipientpubkey)
|
||||
|
||||
blocknum = 7
|
||||
lockduration = 10
|
||||
blocknum = proxy.getblockcount()
|
||||
redeemblocknum = blocknum + lockduration
|
||||
# Create a htlc redeemScript. Similar to a scriptPubKey the redeemScript must be
|
||||
# satisfied for the funds to be spent.
|
||||
txin_redeemScript = CScript([OP_IF, OP_SHA256, h, OP_EQUALVERIFY,OP_DUP, OP_HASH160,
|
||||
recipientpubkey, OP_ELSE, blocknum, OP_DROP, OP_HASH160,
|
||||
recipientpubkey, OP_ELSE, redeemblocknum, OP_CHECKLOCKTIMEVERIFY, OP_DROP, OP_DUP, OP_HASH160,
|
||||
senderpubkey, OP_ENDIF,OP_EQUALVERIFY, OP_CHECKSIG])
|
||||
|
||||
print("redeem script:", b2x(txin_redeemScript))
|
||||
|
||||
# Create P2SH scriptPubKey from redeemScript.
|
||||
|
@ -53,8 +56,10 @@ print('Pay to:', p2sh)
|
|||
|
||||
# AUTOMATE Send funds to p2sh
|
||||
amount = 1.0*COIN
|
||||
# sendtoaddress return the id of the created tx
|
||||
fund_tx = proxy.sendtoaddress(txin_p2sh_address, amount)
|
||||
|
||||
print('fund tx sent. It\'s id is:', b2x(lx(b2x(fund_tx))))
|
||||
print('Now redeeming.........')
|
||||
|
||||
# AUTOMATE getting vout of funding tx
|
||||
|
@ -92,4 +97,4 @@ VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,))
|
|||
|
||||
print("Now sending redeem transaction.......")
|
||||
txid = proxy.sendrawtransaction(tx)
|
||||
print("Txid of submitted redeem tx: ", b2x(txid))
|
||||
print("Txid of submitted redeem tx: ", b2x(lx(b2x(txid))))
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
#!/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)
|
|
@ -0,0 +1 @@
|
|||
{"t2QrLFUqmp1v1xQSE3hmgwcYuinRb3BRWMm": {"funder": "tmTjZSg4pX2Us6V5HttiwFZwj464fD2ZgpY", "redeemer": "tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ", "redeemblocknum": 182, "zec_redeemScript": "63a820343e398e4e99a68e7de6ec57f00b6a14a8e6d0a7dd714efbab4dcbd385f4f3038876a9143ea29256c9d2888ca23de42a8b8e69ca2ec235b16702b600b17576a914c5acca6ef39c843c7a9c3ad01b2da95fe2edf5ba6888ac", "p2sh": "t2QrLFUqmp1v1xQSE3hmgwcYuinRb3BRWMm"}, "2MuWU5BpLpqJvvzkCPq8gFHA4VFFGyvjaJf": {"funder": "myfFr5twPYNwgeXyjCmGcrzXtCmfmWXKYp", "redeemer": "mrQzUGU1dwsWRx5gsKKSDPNtrsP65vCA3Z", "redeemblocknum": 146, "zec_redeemScript": "63a820343e398e4e99a68e7de6ec57f00b6a14a8e6d0a7dd714efbab4dcbd385f4f3038876a9147788b4511a25fba1092e67b307a6dcdb6da125d967029200b17576a914c7043e62a7391596116f54f6a64c8548e97d3fd96888ac", "p2sh": "2MuWU5BpLpqJvvzkCPq8gFHA4VFFGyvjaJf"}}
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
|
||||
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
|
|
@ -0,0 +1,106 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Based on spend-p2sh-txout.py from python-bitcoinlib.
|
||||
# Copyright (C) 2017 The Zcash developers
|
||||
|
||||
import sys
|
||||
if sys.version_info.major < 3:
|
||||
sys.stderr.write('Sorry, Python 3.x required by this example.\n')
|
||||
sys.exit(1)
|
||||
|
||||
import bitcoin
|
||||
import bitcoin.rpc
|
||||
from bitcoin import SelectParams
|
||||
from bitcoin.core import b2x, lx, COIN, COutPoint, CMutableTxOut, CMutableTxIn, CMutableTransaction, Hash160
|
||||
from bitcoin.core.script import CScript, OP_DUP, OP_IF, OP_ELSE, OP_ENDIF, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG, SignatureHash, SIGHASH_ALL
|
||||
from bitcoin.core.script import OP_DROP, OP_CHECKLOCKTIMEVERIFY, OP_SHA256, OP_TRUE, OP_FALSE
|
||||
from bitcoin.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH
|
||||
from bitcoin.wallet import CBitcoinAddress, CBitcoinSecret
|
||||
|
||||
import hashlib
|
||||
|
||||
# SelectParams('testnet')
|
||||
# To get transactions not in your wallet, must set -txindex=1
|
||||
SelectParams('regtest')
|
||||
proxy = bitcoin.rpc.Proxy()
|
||||
|
||||
# The parameters needed for the htlc - hash preimage, sender/seller address, recipient/buyer address, num of blocks for timeout
|
||||
preimage = b'preimage'
|
||||
h = hashlib.sha256(preimage).digest()
|
||||
|
||||
# proxy.getnewaddress() returns CBitcoinAddress
|
||||
recipientpubkey = proxy.getnewaddress()
|
||||
senderpubkey = proxy.getnewaddress()
|
||||
# privkey of the recipient, used to sign the redeemTx
|
||||
seckey = proxy.dumpprivkey(senderpubkey)
|
||||
#proxy.importprivkey(seckey)
|
||||
lockduration = 10
|
||||
blocknum = proxy.getblockcount()
|
||||
print("current block num:", blocknum)
|
||||
redeemblocknum = blocknum + lockduration
|
||||
# Create a htlc redeemScript. Similar to a scriptPubKey the redeemScript must be
|
||||
# satisfied for the funds to be spent.
|
||||
txin_redeemScript = CScript([OP_IF, OP_SHA256, h, OP_EQUALVERIFY,OP_DUP, OP_HASH160,
|
||||
recipientpubkey, OP_ELSE, redeemblocknum, OP_CHECKLOCKTIMEVERIFY, OP_DROP, OP_DUP, OP_HASH160,
|
||||
senderpubkey, OP_ENDIF,OP_EQUALVERIFY, OP_CHECKSIG])
|
||||
print("redeem script:", b2x(txin_redeemScript))
|
||||
|
||||
# Create P2SH scriptPubKey from redeemScript.
|
||||
txin_scriptPubKey = txin_redeemScript.to_p2sh_scriptPubKey()
|
||||
print("p2sh_scriptPubKey", b2x(txin_scriptPubKey))
|
||||
|
||||
# Convert the P2SH scriptPubKey to a base58 Bitcoin address
|
||||
txin_p2sh_address = CBitcoinAddress.from_scriptPubKey(txin_scriptPubKey)
|
||||
p2sh = str(txin_p2sh_address)
|
||||
print('Pay to:', p2sh)
|
||||
|
||||
# AUTOMATE Send funds to p2sh
|
||||
amount = 1.0*COIN
|
||||
fund_tx = proxy.sendtoaddress(txin_p2sh_address, amount)
|
||||
|
||||
print('Now redeeming.........')
|
||||
|
||||
print('Jumping forward till after timelock')
|
||||
proxy.generate(lockduration)
|
||||
print('block num is now:', proxy.getblockcount())
|
||||
# AUTOMATE getting vout of funding tx
|
||||
txinfo = proxy.gettransaction(fund_tx)
|
||||
details = txinfo['details'][0]
|
||||
vout = details['vout']
|
||||
|
||||
# Create the txin structure. scriptSig defaults to being empty.
|
||||
# The input is the p2sh funding transaction txid, vout is its index
|
||||
txin = CMutableTxIn(COutPoint(fund_tx, vout))
|
||||
|
||||
# The default nSequence of FFFFFFFF won't let you redeem when there's a CHECKTIMELOCKVERIFY
|
||||
txin.nSequence = 0
|
||||
|
||||
# Create the txout. Pays out to recipient, so uses recipient's pubkey
|
||||
# Withdraw full amount minus fee
|
||||
default_fee = 0.001*COIN
|
||||
txout = CMutableTxOut(amount - default_fee, senderpubkey.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
|
||||
tx.nLockTime=redeemblocknum
|
||||
|
||||
# Calculate the signature hash for that transaction. Note how the script we use
|
||||
# is the redeemScript, not the scriptPubKey. EvalScript() will be evaluating the redeemScript
|
||||
sighash = SignatureHash(txin_redeemScript, tx, 0, SIGHASH_ALL)
|
||||
|
||||
# Now sign it. We have to append the type of signature we want to the end, in
|
||||
# this case the usual SIGHASH_ALL.
|
||||
sig = seckey.sign(sighash) + bytes([SIGHASH_ALL])
|
||||
|
||||
# Set the scriptSig of our transaction input appropriately.
|
||||
txin.scriptSig = CScript([sig, seckey.pub, OP_FALSE, txin_redeemScript])
|
||||
|
||||
print("Redeem tx hex:", b2x(tx.serialize()))
|
||||
|
||||
# Verify the signature worked.
|
||||
VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,))
|
||||
|
||||
print("Now sending redeem transaction.......")
|
||||
txid = proxy.sendrawtransaction(tx)
|
||||
print("Txid of submitted redeem tx: ", b2x(lx(b2x(txid))))
|
|
@ -0,0 +1,33 @@
|
|||
import hashlib
|
||||
import json
|
||||
|
||||
def sha256(secret):
|
||||
preimage = secret.encode('utf8')
|
||||
h = hashlib.sha256(preimage).digest()
|
||||
return h
|
||||
|
||||
# TODO: Port these over to leveldb or some other database
|
||||
def save_trade(trade):
|
||||
with open('xcat.json', 'w') as outfile:
|
||||
json.dump(trade, outfile)
|
||||
|
||||
def get_trade():
|
||||
with open('xcat.json') as data_file:
|
||||
try:
|
||||
xcatdb = json.load(data_file)
|
||||
return xcatdb
|
||||
except:
|
||||
return None
|
||||
|
||||
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
|
||||
|
||||
def save_contract(contracts):
|
||||
with open('contract.json', 'w') as outfile:
|
||||
json.dump(contracts, outfile)
|
|
@ -0,0 +1,2 @@
|
|||
7af1b1d333431217958032adc11b84278939d400a7368d33bab4e8455b4203f0
|
||||
7997c2eafeda0815b840bfbd60e5ad70d8879aac252bd594c732a04d6a7b85e9
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
# /home/jay/Zcash/python3-xcat/protocol/checktx.py $@
|
||||
echo "$@" >> "/home/jay/Zcash/python3-xcat/protocol/watchdata"
|
|
@ -0,0 +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"}}
|
|
@ -0,0 +1,278 @@
|
|||
import zXcat
|
||||
import bXcat
|
||||
from utils import *
|
||||
from waiting import *
|
||||
from time import sleep
|
||||
import json
|
||||
import os, sys
|
||||
from pprint import pprint
|
||||
|
||||
def check_p2sh(currency, address):
|
||||
if currency == 'bitcoin':
|
||||
print("Checking funds in Bitcoin p2sh")
|
||||
return bXcat.check_funds(address)
|
||||
else:
|
||||
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)
|
||||
else:
|
||||
sell_p2sh = zXcat.hashtimelockcontract(funder, redeemer, secret, locktime)
|
||||
return sell_p2sh
|
||||
|
||||
def fund_htlc(currency, p2sh, amount):
|
||||
if currency == 'bitcoin':
|
||||
txid = bXcat.fund_htlc(p2sh, amount)
|
||||
else:
|
||||
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
|
||||
|
||||
# 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)
|
||||
|
||||
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")
|
||||
|
||||
sell_amt = trade['sell']['amount']
|
||||
txid = fund_htlc(currency, sell_p2sh, sell_amt)
|
||||
|
||||
trade['sell']['p2sh'] = sell_p2sh
|
||||
trade['sell']['fund_tx'] = txid
|
||||
trade['sell']['status'] = 'funded'
|
||||
# TODO: Save secret locally for seller
|
||||
trade['sell']['secret'] = secret
|
||||
|
||||
save_trade(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
|
||||
if currency == 'bitcoin':
|
||||
res = bXcat.redeem(p2sh, action)
|
||||
else:
|
||||
res = zXcat.redeem(p2sh, action)
|
||||
return res
|
||||
|
||||
def seller_redeem():
|
||||
# add locktime as variable?
|
||||
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)
|
||||
|
||||
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']))
|
||||
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)
|
||||
|
||||
def print_trade(role):
|
||||
print("Trade status:")
|
||||
trade = get_trade()
|
||||
if role == 'seller':
|
||||
pprint(trade)
|
||||
else:
|
||||
del trade['sell']['secret']
|
||||
pprint(trade)
|
||||
|
||||
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
|
||||
trade = get_trade()
|
||||
|
||||
try:
|
||||
if sys.argv[1] == 'new':
|
||||
erase_trade()
|
||||
role = 'seller'
|
||||
trade = get_trade()
|
||||
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 trade is not None:
|
||||
if trade['sell']['status'] == 'redeemed' and trade['buy']['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')
|
||||
else:
|
||||
# Need better way of preventing buyer from having secret
|
||||
if 'status' not in trade['buy'] and trade['sell']['status'] == 'funded':
|
||||
print("One active trade available, fulfilling buyer contract...")
|
||||
trade = get_trade()
|
||||
buyer_fulfill()
|
||||
# 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':
|
||||
# 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()
|
||||
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
|
|
@ -0,0 +1,137 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Based on spend-p2sh-txout.py from python-bitcoinlib.
|
||||
# Copyright (C) 2017 The Zcash developers
|
||||
|
||||
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
|
||||
|
||||
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, secret, locktime):
|
||||
funderAddr = CBitcoinAddress(funder)
|
||||
redeemerAddr = CBitcoinAddress(redeemer)
|
||||
h = sha256(secret)
|
||||
blocknum = zcashd.getblockcount()
|
||||
redeemblocknum = blocknum + locktime
|
||||
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])
|
||||
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 {'p2sh': p2sh, 'redeemblocknum': redeemblocknum, 'zec_redeemScript': b2x(zec_redeemScript), 'redeemer': redeemer, 'funder': funder}
|
||||
|
||||
def fund_htlc(p2sh, amount):
|
||||
send_amount = 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)
|
||||
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 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']
|
||||
|
||||
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))
|
||||
|
||||
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])
|
||||
|
||||
# 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)
|
||||
|
||||
# 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
|
||||
txin_scriptPubKey = zec_redeemScript.to_p2sh_scriptPubKey()
|
||||
|
||||
# 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
|
||||
else:
|
||||
print("No contract for this p2sh found in database", p2sh)
|
||||
|
||||
|
||||
def new_zcash_addr():
|
||||
addr = zcashd.getnewaddress()
|
||||
print('new ZEC addr', addr.to_p2sh_scriptPubKey)
|
||||
return addr.to_scriptPubKey()
|
|
@ -0,0 +1,139 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Based on spend-p2sh-txout.py from python-bitcoinlib.
|
||||
# Copyright (C) 2017 The Zcash developers
|
||||
|
||||
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, 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
|
||||
import hashlib
|
||||
|
||||
# SelectParams('testnet')
|
||||
SelectParams('regtest')
|
||||
zcashd = zcash.rpc.Proxy()
|
||||
FEE = 0.001*COIN
|
||||
|
||||
# ========================= ZCASH ADDRESSES =========================
|
||||
alice_address = input("Enter alice zcash address: (type 'enter' for demo)")
|
||||
bob_address = input("Enter bob zcash address: (type 'enter' for demo)")
|
||||
# These mock addresses come from regtest on the server
|
||||
# alicepubkey = CBitcoinAddress('tmFUm31B9wzHWJ9jGe9L9Qb549zfC7zFsEK')
|
||||
# bobpubkey = CBitcoinAddress('tmFm8R6b22485uDYm6dryC4f8R6oXUTUe5i')
|
||||
# zcashd.getnewaddress() returns CBitcoinAddress
|
||||
bobpubkey = zcashd.getnewaddress()
|
||||
alicepubkey = zcashd.getnewaddress()
|
||||
print("alicepubkey", alicepubkey)
|
||||
print("bobpubkey", bobpubkey)
|
||||
# privkey of the bob, used to sign the redeemTx
|
||||
bob_seckey = zcashd.dumpprivkey(bobpubkey)
|
||||
# privkey of alice, used to refund tx in case of timeout
|
||||
alice_seckey = zcashd.dumpprivkey(alicepubkey)
|
||||
print("bob_seckey", bob_seckey)
|
||||
|
||||
def get_keys(funder_address, redeemer_address):
|
||||
# fundpubkey = CBitcoinAddress(funder_address)
|
||||
# redeempubkey = CBitcoinAddress(redeemer_address)
|
||||
fundpubkey = zcashd.getnewaddress()
|
||||
redeempubkey = zcashd.getnewaddress()
|
||||
return fundpubkey, redeempubkey
|
||||
|
||||
# ======= secret from Alice, other file ====
|
||||
preimage = b'preimage'
|
||||
h = hashlib.sha256(preimage).digest()
|
||||
|
||||
# ========================= LOCKTIME SCRIPT CREATION =========================
|
||||
lockduration = 20 # Must be more than first tx
|
||||
blocknum = zcashd.getblockcount()
|
||||
redeemblocknum = blocknum + lockduration
|
||||
zec_redeemScript = CScript([OP_IF, OP_SHA256, h, OP_EQUALVERIFY,OP_DUP, OP_HASH160,
|
||||
bobpubkey, OP_ELSE, redeemblocknum, OP_CHECKLOCKTIMEVERIFY, OP_DROP, OP_DUP, OP_HASH160,
|
||||
alicepubkey, OP_ENDIF,OP_EQUALVERIFY, OP_CHECKSIG])
|
||||
print("TX2 Redeem script on Zcash blockchain:", b2x(zec_redeemScript))
|
||||
|
||||
# ========================= TX1: CREATE BITCOIN P2SH FROM SCRIPT =========================
|
||||
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)
|
||||
print('Alice -- send funds to this p2sh address to initiate atomic swap:', p2sh)
|
||||
|
||||
response = input("Alice -- Type 'enter' to allow zbxcat to fund the Zcash p2sh on your behalf:")
|
||||
send_amount = 10.0*COIN
|
||||
fund_tx = zcashd.sendtoaddress(txin_p2sh_address, send_amount)
|
||||
print('Bob -- Alice send fund tx to the Zcash p2sh. Please wait for confirmation. Txid:', b2x(lx(b2x(fund_tx))))
|
||||
|
||||
# ========================= CONFIRM ZCASH FUNDING TX TO P2SH =========================
|
||||
zcashd.importaddress(p2sh)
|
||||
|
||||
fund_txinfo = zcashd.gettransaction(fund_tx)
|
||||
fund_details = fund_txinfo['details'] # "fund_details" is an array, for now we can assume it only has one destination address
|
||||
outputAddress = fund_details[0]['address']
|
||||
fund_vout = fund_details[0]['vout']
|
||||
if (outputAddress != p2sh):
|
||||
print('Fund tx sent to wrong address! p2sh was {0}, funding tx was sent to {1}'.format(p2sh, outputAddress))
|
||||
quit()
|
||||
# Get amount in address
|
||||
output_amount = zcashd.getreceivedbyaddress(outputAddress, 0)
|
||||
if (output_amount < send_amount):
|
||||
print('Fund tx too small! Amount sent was {0}, amount expected was {1}'.format(output_amount, send_amount))
|
||||
quit()
|
||||
|
||||
print('Bob -- Alice Zcash funding tx confirmed, now send funds to the Bitcoin p2sh: (other file)')
|
||||
|
||||
# ========================= PART 2: ZCASH P2SH FUNDED, REDEEM OR REFUND =========================
|
||||
|
||||
# ================= AFTER 48 HRS: ALICE REFUNDS AFTER BOB TIMES OUT =========================
|
||||
# Mock passage of time -- comment out to test normal redeem condition
|
||||
# zcashd.generate(25)
|
||||
|
||||
if(zcashd.getblockcount() >= redeemblocknum):
|
||||
print("Alice -- Bob did not redeem the Zcash you put in escrow within the timeout period, so refunding you..... ")
|
||||
txin = CMutableTxIn(COutPoint(fund_tx, fund_vout))
|
||||
# The default nSequence of FFFFFFFF won't let you redeem when there's a CHECKTIMELOCKVERIFY
|
||||
txin.nSequence = 0
|
||||
txout = CMutableTxOut(send_amount - FEE, alicepubkey.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
|
||||
tx.nLockTime = redeemblocknum
|
||||
# Calculate the signature hash for that transaction. Note how the script we use
|
||||
# is the redeemScript, not the scriptPubKey. EvalScript() will be evaluating the redeemScript
|
||||
sighash = SignatureHash(zec_redeemScript, tx, 0, SIGHASH_ALL)
|
||||
sig = alice_seckey.sign(sighash) + bytes([SIGHASH_ALL])
|
||||
txin.scriptSig = CScript([sig, alice_seckey.pub, OP_FALSE, zec_redeemScript])
|
||||
print("Time lock has passed, Alice redeeming her own tx:")
|
||||
print("Refund tx hex:", b2x(tx.serialize()))
|
||||
VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,))
|
||||
txid = zcashd.sendrawtransaction(tx)
|
||||
print("Txid of submitted refund tx: ", b2x(lx(b2x(txid))))
|
||||
quit()
|
||||
|
||||
# ================= BEFORE 48 HRS: BOB REDEEMS WITH ALICE'S REVEALED SECRET =========================
|
||||
print("Bob -- Redeeming tx.....")
|
||||
txin = CMutableTxIn(COutPoint(fund_tx, fund_vout))
|
||||
txout = CMutableTxOut(send_amount - FEE, bobpubkey.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
|
||||
tx.nLockTime = redeemblocknum
|
||||
sighash = SignatureHash(zec_redeemScript, tx, 0, SIGHASH_ALL)
|
||||
sig = bob_seckey.sign(sighash) + bytes([SIGHASH_ALL])
|
||||
txin.scriptSig = CScript([sig, bob_seckey.pub, preimage, OP_TRUE, zec_redeemScript])
|
||||
print("Redeem tx hex:", b2x(tx.serialize()))
|
||||
|
||||
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,))
|
||||
|
||||
txid = zcashd.sendrawtransaction(tx)
|
||||
print("Txid of submitted redeem tx: ", b2x(lx(b2x(txid))))
|
Loading…
Reference in New Issue