commit 3e1bb1fbaf88bd578f00e81c35137479552724b1 Author: Jay Graber Date: Tue May 9 16:19:03 2017 -0700 Add scripts and daemon code diff --git a/ZBXCAT/BitcoinRPC/BDaemon.py b/ZBXCAT/BitcoinRPC/BDaemon.py new file mode 100644 index 0000000..97f1f66 --- /dev/null +++ b/ZBXCAT/BitcoinRPC/BDaemon.py @@ -0,0 +1,103 @@ +from __future__ import print_function +from __future__ import absolute_import +import requests +import json + +from .settings import * + +class BDaemon(object): + + id_count = 0 + + def __init__(self, network=NETWORK, user=RPCUSER, password=RPCPASSWORD, timeout=TIMEOUT): + #TODO: check utf safety + url = network_url(network) + print('In mode:', network) + self.network = url + self.user = user.encode('utf8') + self.password = password.encode('utf8') + self.timeout = timeout + + + def _call(self, method, *args): + jsondata = json.dumps({ 'version': '2', + 'method': method, + 'params': args, + 'id': self.id_count}) + + r = requests.post(self.network, auth=(self.user,self.password), data=jsondata, timeout=self.timeout) + + self.id_count += 1 + + resp = json.loads(r.text) + + #TODO: deal with errors better. + error = resp['error'] + if error: + print(error) + + return resp['result'] + + # REGTEST + def generate(self, blocknum): + return self._call('generate', blocknum) + + def importaddress(self, script): + return self._call('importaddress', script) + + #Block Info + def getBlockHash(self, blockheight): + return self._call('getblockhash', blockheight) + + def getBlockByHash(self, blockhash): + return self._call('getblock', blockhash) + + def getBlockByHeight(self, blockheight): + return self.getBlockByHash(self.getBlockHash(blockheight)) + + # Custom methods to get Network Info + def getNetworkHeight(self): + return self._call('getblockcount') + + def getNetworkDifficulty(self): + return self._call('getdifficulty') + + def getVersion(self): + info = self._call('getnetworkinfo') + client = info['subversion'] + version = client.strip('/').split(':')[1] + return version + + def getConnectionCount(self): + return self._call('getconnectioncount') + + # Wallet Info + def getbalance(self): + return self._call('getbalance') + + def listunspent(self, minconf=1): + return self._call('listunspent', minconf) + + #Raw Txs + def gettransaction(self, txid): + return self._call('gettransaction', txid) + + def getrawtransaction(self, txid, verbose=0): + # default verbose=0 returns serialized, hex-encoded data + # verbose=1, returns a JSON obj of tx + return self._call('getrawtransaction', txid, verbose) + + def decoderawtransaction(self, txhex): + return self._call('decoderawtransaction', txhex) + + def sendrawtransaction(self, txhex): + return self._call('sendrawtransaction', txhex) + + def getnewaddress(self): + return self._call('getnewaddress') + + def sendtoaddress(self, taddress, amount): + return self._call('sendtoaddress', taddress, amount) + + def listunspent(self): + return self._call('listunspent') diff --git a/ZBXCAT/BitcoinRPC/__init__.py b/ZBXCAT/BitcoinRPC/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ZBXCAT/BitcoinRPC/settings.py b/ZBXCAT/BitcoinRPC/settings.py new file mode 100644 index 0000000..1c2868e --- /dev/null +++ b/ZBXCAT/BitcoinRPC/settings.py @@ -0,0 +1,28 @@ +import os, re + +TIMEOUT = 100 +#Default fee to use on network for txs. +DEFAULT_FEE = 0.01 +# Default network is testnet +NETWORK = 'TESTNET' +def network_url(network): + if network == 'TESTNET': + return "http://localhost:18332" + if network == 'REGTEST': + return "http://localhost:18332" + if network == 'MAINNET': + return "http://localhost:8332" + +bitcoinconf = os.path.expanduser('~/.bitcoin/bitcoin.conf') +def read_config(filename): + f = open(filename) + for line in f: + if re.match('rpcuser', line): + user = line.strip('\n').split('=')[1] + if re.match('rpcpassword', line): + password = line.strip('\n').split('=')[1] + return (user, password) +config = read_config(bitcoinconf) +# from bitcoin.conf +RPCUSER = config[0] +RPCPASSWORD = config[1] diff --git a/ZBXCAT/README.md b/ZBXCAT/README.md new file mode 100644 index 0000000..c47544f --- /dev/null +++ b/ZBXCAT/README.md @@ -0,0 +1 @@ +Python wrapper for bitcoind and zcashd, created for ZBXCAT - Zcash Bitcoin Cross-Chain Atomic Transactions. diff --git a/ZBXCAT/ZcashRPC/ZDaemon.py b/ZBXCAT/ZcashRPC/ZDaemon.py new file mode 100644 index 0000000..cbe9a28 --- /dev/null +++ b/ZBXCAT/ZcashRPC/ZDaemon.py @@ -0,0 +1,160 @@ +from __future__ import print_function +from __future__ import absolute_import +import requests +import json + +from .settings import * + +class ZDaemon(object): + + id_count = 0 + + def __init__(self, network=NETWORK, user=RPCUSER, password=RPCPASSWORD, timeout=TIMEOUT): + #TODO: check utf safety + url = network_url(network) + print('In mode:', network) + self.network = url + self.user = user.encode('utf8') + self.password = password.encode('utf8') + self.timeout = timeout + + + def _call(self, method, *args): + jsondata = json.dumps({ 'version': '2', + 'method': method, + 'params': args, + 'id': self.id_count}) + + print("jsondata", jsondata) + + r = requests.post(self.network, auth=(self.user,self.password), data=jsondata, timeout=self.timeout) + + self.id_count += 1 + + resp = json.loads(r.text) + + #TODO: deal with errors better. + error = resp['error'] + if error: + print(error) + + return resp['result'] + + # REGTEST + def generate(self, blocknum): + return self._call('generate', blocknum) + + def importaddress(self, script): + return self._call('importaddress', script) + + #Block Info + def getBlockHash(self, blockheight): + return self._call('getblockhash', blockheight) + + def getBlockByHash(self, blockhash): + return self._call('getblock', blockhash) + + def getBlockByHeight(self, blockheight): + return self.getBlockByHash(self.getBlockHash(blockheight)) + + # Custom methods to get Network Info + def getNetworkHeight(self): + return self._call('getblockcount') + + def getNetworkDifficulty(self): + return self._call('getdifficulty') + + def getVersion(self): + info = self._call('getnetworkinfo') + client = info['subversion'] + version = client.strip('/').split(':')[1] + return version + + def getConnectionCount(self): + return self._call('getconnectioncount') + + # Wallet Info (transparent) + def getbalance(self): + return self._call('getbalance') + + def listunspent(self, minconf=1): + return self._call('listunspent', minconf) + + #Raw Txs + def gettransaction(self, txid): + return self._call('gettransaction', txid) + + def getrawtransaction(self, txid, verbose=0): + # default verbose=0 returns serialized, hex-encoded data + # verbose=1, returns a JSON obj of tx + return self._call('getrawtransaction', txid, verbose) + + def decoderawtransaction(self, txhex): + return self._call('decoderawtransaction', txhex) + + def sendrawtransaction(self, txhex): + return self._call('sendrawtransaction', txhex) + + # taddr methods + def getnewaddress(self): + return self._call('getnewaddress') + + def sendtoaddress(self, taddress, amount): + return self._call('sendtoaddress', taddress, amount) + + def listunspent(self): + return self._call('listunspent') + + # Custom method to find a taddr with spendable utxos for z_sendmany + def find_taddr_with_unspent(self): + unspent = self._call('listunspent') + for utxo in unspent: + if utxo['spendable'] == True and utxo['amount'] > 0.1: + # Check that it's not a coinbase tx + tx = zd.gettransaction(utxo['txid']) + if 'generated' not in tx: + return tx['address'] + + def sweep_coinbase(self, zaddr): + cb = [] + utxos = self.listunspent() + for utxo in utxos: + tx = self.gettransaction(utxo['txid']) + if 'generated' in tx and tx['generated'] == True: + cb.append(utxo) + for coin in cb: + amount = coin['amount'] - 0.0001 + opid = self.z_sendmany(coin['address'], zaddr, amount) + print("OPID of z_sendmany: ", opid) + status = self.z_getoperationstatus(opid) + print("Status: ", status[0]['status']) + + # zaddr methods + def z_gettotalbalance(self): + return self._call('z_gettotalbalance') + + def z_getnewaddress(self): + return self._call('z_getnewaddress') + + def z_listaddresses(self): + return self._call('z_listaddresses') + + def z_listreceivedbyaddress(self, zaddr, minconf=1): + return self._call('z_listreceivedbyaddress', zaddr, minconf) + + def z_getoperationstatus(self, opid): + return self._call('z_getoperationstatus', ["{0}".format(opid)]) + + def z_getoperationresult(self, opid): + return self._call('z_getoperationresult', ["{0}".format(opid)]) + + # With addition of encrypted memo field + def z_sendmany(self, sender, receiver, amount=0.0001, memo=''): + amts_array = [] + if memo == '': + amounts = {"address": receiver, "amount": amount} + else: + memo = memo.encode('hex') + amounts = {"address": receiver, "amount": amount, "memo": memo} + amts_array.append(amounts) + return self._call('z_sendmany', sender, amts_array) diff --git a/ZBXCAT/ZcashRPC/ZDaemon.pyc b/ZBXCAT/ZcashRPC/ZDaemon.pyc new file mode 100644 index 0000000..0b074e6 Binary files /dev/null and b/ZBXCAT/ZcashRPC/ZDaemon.pyc differ diff --git a/ZBXCAT/ZcashRPC/__init__.py b/ZBXCAT/ZcashRPC/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ZBXCAT/ZcashRPC/__init__.pyc b/ZBXCAT/ZcashRPC/__init__.pyc new file mode 100644 index 0000000..e7684c8 Binary files /dev/null and b/ZBXCAT/ZcashRPC/__init__.pyc differ diff --git a/ZBXCAT/ZcashRPC/__pycache__/ZDaemon.cpython-35.pyc b/ZBXCAT/ZcashRPC/__pycache__/ZDaemon.cpython-35.pyc new file mode 100644 index 0000000..63efae8 Binary files /dev/null and b/ZBXCAT/ZcashRPC/__pycache__/ZDaemon.cpython-35.pyc differ diff --git a/ZBXCAT/ZcashRPC/__pycache__/__init__.cpython-35.pyc b/ZBXCAT/ZcashRPC/__pycache__/__init__.cpython-35.pyc new file mode 100644 index 0000000..4edf664 Binary files /dev/null and b/ZBXCAT/ZcashRPC/__pycache__/__init__.cpython-35.pyc differ diff --git a/ZBXCAT/ZcashRPC/__pycache__/settings.cpython-35.pyc b/ZBXCAT/ZcashRPC/__pycache__/settings.cpython-35.pyc new file mode 100644 index 0000000..7be1a02 Binary files /dev/null and b/ZBXCAT/ZcashRPC/__pycache__/settings.cpython-35.pyc differ diff --git a/ZBXCAT/ZcashRPC/settings.py b/ZBXCAT/ZcashRPC/settings.py new file mode 100644 index 0000000..1e5270a --- /dev/null +++ b/ZBXCAT/ZcashRPC/settings.py @@ -0,0 +1,38 @@ +import os, re + +#Timeout needs to be high for any pour operations +TIMEOUT = 600 +#Default fee to use on network for txs. +DEFAULT_FEE = 0.01 + +# Default is testnet +NETWORK = 'TESTNET' +def network_url(network): + if network == 'TESTNET': + return "http://localhost:18232" + if network == 'REGTEST': + return "http://localhost:18232" + if network == 'MAINNET': + return "http://localhost:18444" + +zcashconf = os.path.expanduser('~/.zcash/zcash.conf') +def read_config(filename): + f = open(filename) + for line in f: + if re.match('rpcuser', line): + user = line.strip('\n').split('=')[1] + if re.match('rpcpassword', line): + password = line.strip('\n').split('=')[1] + return (user, password) +config = read_config(zcashconf) +# from zcash conf +RPCUSER = config[0] +RPCPASSWORD = config[1] + + +#TESTS +#for tests (sample data here - replace with your own) +TEST_TXID = '' +TEST_ZADDR = "" +TEST_TADDR = "" +TEST_ZSECRET = "" diff --git a/ZBXCAT/ZcashRPC/settings.pyc b/ZBXCAT/ZcashRPC/settings.pyc new file mode 100644 index 0000000..b4a4c1d Binary files /dev/null and b/ZBXCAT/ZcashRPC/settings.pyc differ diff --git a/ZBXCAT/__init__.py b/ZBXCAT/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ZBXCAT/__init__.pyc b/ZBXCAT/__init__.pyc new file mode 100644 index 0000000..3e2ddb4 Binary files /dev/null and b/ZBXCAT/__init__.pyc differ diff --git a/ZBXCAT/__pycache__/__init__.cpython-35.pyc b/ZBXCAT/__pycache__/__init__.cpython-35.pyc new file mode 100644 index 0000000..6565f3f Binary files /dev/null and b/ZBXCAT/__pycache__/__init__.cpython-35.pyc differ diff --git a/ZBXCAT/examples/__init__.py b/ZBXCAT/examples/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ZBXCAT/requirements.txt b/ZBXCAT/requirements.txt new file mode 100644 index 0000000..a8ffe71 --- /dev/null +++ b/ZBXCAT/requirements.txt @@ -0,0 +1,2 @@ +requests +python-bitcoinlib diff --git a/ZBXCAT/tests.py b/ZBXCAT/tests.py new file mode 100644 index 0000000..3ae4048 --- /dev/null +++ b/ZBXCAT/tests.py @@ -0,0 +1,38 @@ +from __future__ import print_function +from __future__ import absolute_import +import os.path +import sys + +from .rpc.ZDaemon import * +from settings import * + +def test_daemon(): + zd = ZDaemon() + + # Network tests + print(zd.getBlockHash(100)) + print(zd.getBlockByHash(zd.getBlockHash(100))) + print(zd.getBlockByHeight(100)) + print(zd.getNetworkHeight()) + print(zd.getNetworkDifficulty()) + print(zd.getVersion()) + print(zd.getConnectionCount()) + + # Taddr Wallet tests + print(zd.getbalance()) + print(zd.listunspent()) + print(zd.getnewaddress()) + + # Zaddr wallet tests + print(zd.z_getnewaddress()) + zaddrs = zd.z_listaddresses() + print(zaddrs) + zaddr_received = zd.z_listreceivedbyaddress(zaddr) + print(zaddr_received) + + # TODO: test z_sendmany, and use regtest + +if __name__ == "__main__": + # test_daemon() + zd = ZDaemon() + print(zd.getVersion()) diff --git a/__pycache__/daemon.cpython-35.pyc b/__pycache__/daemon.cpython-35.pyc new file mode 100644 index 0000000..dfb7921 Binary files /dev/null and b/__pycache__/daemon.cpython-35.pyc differ diff --git a/daemon.py b/daemon.py new file mode 100755 index 0000000..501e572 --- /dev/null +++ b/daemon.py @@ -0,0 +1,35 @@ +from ZBXCAT.BitcoinRPC.BDaemon import * + +bd = BDaemon('REGTEST') +# v = bd.getVersion() +# print(v) + +def generate(num): + gen = bd.generate(num) + print("Generated blocks", gen) + +def fund_p2sh(p2sh, amount): + fund_tx = bd.sendtoaddress(p2sh, amount) + return fund_tx + +def tx_details(txid): + tx = bd.gettransaction(txid) + details = tx['details'][0] + return details + +# These two methods are placeholders +def get_recipient_address(): + address = bd.getnewaddress() + return address + +def get_sender_address(): + address = bd.getnewaddress() + return address + +def importaddress(addr): + res = bd.importaddress(addr) + return res + +def sendrawtx(hex): + txid = bd.sendrawtransaction(hex) + return txid diff --git a/redeem-preimage-p2sh.py b/redeem-preimage-p2sh.py new file mode 100755 index 0000000..e8a8f8f --- /dev/null +++ b/redeem-preimage-p2sh.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 + +# Based on spend-p2sh-txout.py from python-bitcoinlib. +# Copyright (C) 2014 The python-bitcoinlib developers +# 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) + +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 +from bitcoin.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH +from bitcoin.wallet import CBitcoinAddress, CBitcoinSecret + +import hashlib +from daemon import * + +SelectParams('testnet') + +# 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() +seckey = CBitcoinSecret('cShLodcaVA7JjDXnRaK5jsmQhPJmBDeXPd4Fzx8dRD9ih6gmXKH6') + +# AUTOMATE - get recipient addr +recipient_address = get_recipient_address() +print(' recipient_address (newly generated)', recipient_address) +sender_address = get_sender_address() +print(' sender_address (newly generated)', sender_address) + +recipientpubkey = CBitcoinAddress('mheZcjatFMjcHX5hVQdAY4Lvxm7q7rXuU2') +senderpubkey = CBitcoinAddress('mheZcjatFMjcHX5hVQdAY4Lvxm7q7rXuU2') + +blocknum = 7 +# 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, + senderpubkey, OP_ENDIF,OP_EQUALVERIFY, OP_CHECKSIG]) +print("redeem script:", b2x(txin_redeemScript)) + +#print("scriptpubey:", b2x(recipientpubkey.to_scriptPubKey())) + +# 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 and print it. +# You'll need to send some funds to it to create a txout to spend. +txin_p2sh_address = CBitcoinAddress.from_scriptPubKey(txin_scriptPubKey) +print('Pay to:',str(txin_p2sh_address)) + +p2sh = str(txin_p2sh_address) + +#AUTOMATE funding tx, seller sends funds +amount = 1.0 +fund_tx = fund_p2sh(p2sh, amount) +print("TXID", fund_tx) + +# AUTOMATE import this address? +importaddress(p2sh) +print('p2sh address imported') + +# MINE THE FUNDED TX +generate(1) + +print('Now redeeming.........') + +# lx() takes *little-endian* hex and converts it to bytes; in Bitcoin +# transaction hashes are shown little-endian rather than the usual big-endian. + +# AUTOMATE +txid = lx(fund_tx) +details = tx_details(fund_tx) +print('get details of fund_tx', details) +vout = details['vout'] +print('vout', vout) + +# Create the txin structure, which includes the outpoint. The scriptSig +# defaults to being empty. +txin = CMutableTxIn(COutPoint(txid, vout)) + +# Create the txout. This time we create the scriptPubKey from a Bitcoin +# address. +# AUTOMATE: amount and address set above +redeemed = amount*COIN +fee = 0.001*COIN +txout = CMutableTxOut(redeemed - fee, CBitcoinAddress(recipient_address).to_scriptPubKey()) + +# Create the unsigned 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("scriptSig:", b2x(txin.scriptSig)) + +# Verify the signature worked. +VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,)) + +# Tx to hex +hextx = b2x(tx.serialize()) +print(hextx) + +# AUTOMATE: Send raw redeem tx +txid = sendrawtx(hextx) +print("txid of submitted redeem tx", txid) + +generate(1) diff --git a/xcat_htlc_zec.py b/xcat_htlc_zec.py new file mode 100644 index 0000000..de353ea --- /dev/null +++ b/xcat_htlc_zec.py @@ -0,0 +1,70 @@ +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