Add scripts and daemon code

This commit is contained in:
Jay Graber 2017-05-09 16:19:03 -07:00
commit 3e1bb1fbaf
23 changed files with 596 additions and 0 deletions

View File

@ -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')

View File

View File

@ -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]

1
ZBXCAT/README.md Normal file
View File

@ -0,0 +1 @@
Python wrapper for bitcoind and zcashd, created for ZBXCAT - Zcash Bitcoin Cross-Chain Atomic Transactions.

160
ZBXCAT/ZcashRPC/ZDaemon.py Normal file
View File

@ -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)

BIN
ZBXCAT/ZcashRPC/ZDaemon.pyc Normal file

Binary file not shown.

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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 = ""

Binary file not shown.

0
ZBXCAT/__init__.py Normal file
View File

BIN
ZBXCAT/__init__.pyc Normal file

Binary file not shown.

Binary file not shown.

View File

2
ZBXCAT/requirements.txt Normal file
View File

@ -0,0 +1,2 @@
requests
python-bitcoinlib

38
ZBXCAT/tests.py Normal file
View File

@ -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())

Binary file not shown.

35
daemon.py Executable file
View File

@ -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

121
redeem-preimage-p2sh.py Executable file
View File

@ -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)

70
xcat_htlc_zec.py Normal file
View File

@ -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