From afc3451413b4e3edb499bf31689b3479b554a538 Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Wed, 17 May 2017 15:57:30 -0700 Subject: [PATCH] Refactor xcat and zcash-xcat.py --- xcat.py | 234 +++++++++++++++++--------------------------------- zcash-xcat.py | 127 +++++++++++++++++++++++++++ 2 files changed, 206 insertions(+), 155 deletions(-) create mode 100644 zcash-xcat.py diff --git a/xcat.py b/xcat.py index fe0bc08..149a7d2 100644 --- a/xcat.py +++ b/xcat.py @@ -12,203 +12,127 @@ 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 -from bitcoin.core.script import OP_DROP, OP_CHECKLOCKTIMEVERIFY, OP_SHA256, OP_TRUE +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') -# To get transactions not in your wallet, must set -txindex=1 SelectParams('regtest') -proxy = bitcoin.rpc.Proxy() +bitcoind = bitcoin.rpc.Proxy() +FEE = 0.001*COIN -# The parameters needed for the htlc - hash preimage, sender/seller address, recipient/buyer address, num of blocks for timeout +# ========================= 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() -# proxy.getnewaddress() returns CBitcoinAddress -recipientpubkey = proxy.getnewaddress() -senderpubkey = proxy.getnewaddress() -# privkey of the recipient, used to sign the redeemTx -seckey = proxy.dumpprivkey(recipientpubkey) - +# ========================= LOCKTIME SCRIPT CREATION ========================= lockduration = 10 -blocknum = proxy.getblockcount() +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. -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)) +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('Pay to:', p2sh) +print('Bob -- Assuming Alice has created other tx on Zcash blockchain, send funds to this p2sh address:', p2sh) + +## TODO: IMPORT ZCASH XCAT FUNCTIONS -# AUTOMATE Send funds to p2sh +# ========================= FUND BITCOIN P2SH ========================= +response = input("Bob -- Type 'enter' to allow zbxcat to fund the Bitcoin p2sh on your behalf:") send_amount = 1.0*COIN -# sendtoaddress return the id of the created tx -fund_tx = proxy.sendtoaddress(txin_p2sh_address, send_amount) - -print('fund tx sent. Its id is:', b2x(lx(b2x(fund_tx)))) - - -# Import p2sh address and watch -# proxy.importaddress(p2sh) -# # Returns list of recently observed transaction, includes p2sh if imported and conf sets txindex=1 -# txs = proxy.listtransactions(p2sh, "*", 10, 10, True) -# print('txs from listtransaction', txs) - -# Now receiver receives txid and checks that it is on the blockchain to the right address -txinfo = proxy.gettransaction(fund_tx) -details = txinfo['details'] -print('details', details) # "details" is an array, for now we can assume it only has one destination address -outputAddress = details[0]['address'] -print('outputAddress', outputAddress) -# Let's check amount by importing address and inspecting -proxy.importaddress(outputAddress) -# Get amount in address -output_amount = proxy.getreceivedbyaddress(outputAddress, 0) -print('output amount', output_amount) +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 to wrong address!') + 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 to small!') + 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('sender fund tx has been confirmed, now receiver making their fund tx......') +print('Alice -- the fund tx has been confirmed, now you can redeem your Bitcoin with the secret!') -rec_fund_tx = proxy.sendtoaddress(txin_p2sh_address, send_amount) -print('rec fund tx sent. Its id is:', b2x(lx(b2x(fund_tx)))) -# Now sender checks if the lock time is passed, if so she redeems her own tx -if(proxy.getblockcount()>=redeemblocknum): +# ========================= 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 - # Create the txout. Pays out to recipient, so uses recipient's pubkey - # Withdraw full amount minus fee - default_fee = 0.001*COIN - txout = CMutableTxOut(send_amount - default_fee, senderpubkey.to_scriptPubKey()) + 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 + 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) - sig = seckey.sign(sighash) + bytes([SIGHASH_ALL]) - txin.scriptSig = CScript([sig, seckey.pub, OP_FALSE, txin_redeemScript]) - print("Time lock has passed, sender redeeming their own tx:") - print("Redeem tx hex:", b2x(tx.serialize())) + 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 = proxy.sendrawtransaction(tx) - print("Txid of submitted redeem tx: ", b2x(lx(b2x(txid)))) + txid = bitcoind.sendrawtransaction(tx) + print("Txid of submitted refund tx: ", b2x(lx(b2x(txid)))) quit() -#Otherwise, check that receiver fund tx is on blockchain to correct address with sufficient amount -send_txinfo = proxy.gettransaction(rec_fund_tx) -details = send_txinfo['details'] -print('details', details) # "details" is an array, for now we can assume it only has one destination address -outputAddress = details[0]['address'] -print('outputAddress', outputAddress) -# Let's check amount by importing address and inspecting -proxy.importaddress(outputAddress) -# Get amount in address -output_amount = proxy.getreceivedbyaddress(outputAddress, 0) -print('output amount', output_amount) - -print('output amount', output_amount) -if (outputAddress != p2sh): - print('fund tx to wrong address!') - quit() - -if (output_amount < send_amount): - print('fund tx to small!') - quit() -print('receiver fund tx confirmed, redeeming it with the hash preimage:') - -# Create the txout. Pays out to recipient, so uses recipient's pubkey -# Withdraw full amount minus fee -default_fee = 0.001*COIN -txout = CMutableTxOut(send_amount - default_fee, senderpubkey.to_scriptPubKey()) +# ========================= 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(txin_redeemScript, tx, 0, SIGHASH_ALL) -sig = seckey.sign(sighash) + bytes([SIGHASH_ALL]) -txin.scriptSig = CScript([sig, seckey.pub, OP_FALSE, txin_redeemScript]) +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 = proxy.sendrawtransaction(tx) -print("Txid of submitted redeem tx: ", b2x(lx(b2x(txid)))) - - - - - - - - - - - - - - - - - - - -print('Now redeeming.........') - -# AUTOMATE getting vout of funding tx -txinfo = proxy.gettransaction(fund_tx) -details = txinfo['details'][0] # what is the zero here -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)) - -# Create the txout. Pays out to recipient, so uses recipient's pubkey -# Withdraw full amount minus fee -default_fee = 0.001*COIN -txout = CMutableTxOut(send_amount - default_fee, recipientpubkey.to_scriptPubKey()) - -# Create the unsigned raw transaction. -tx = CMutableTransaction([txin], [txout]) - -# 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, preimage, OP_TRUE, 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) +txid = bitcoind.sendrawtransaction(tx) print("Txid of submitted redeem tx: ", b2x(lx(b2x(txid)))) diff --git a/zcash-xcat.py b/zcash-xcat.py new file mode 100644 index 0000000..bc63d68 --- /dev/null +++ b/zcash-xcat.py @@ -0,0 +1,127 @@ +#!/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) + +# ======= 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())) +VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,)) +txid = bitcoind.sendrawtransaction(tx) +print("Txid of submitted redeem tx: ", b2x(lx(b2x(txid))))