zbxcat/xcat/zcashRPC.py

309 lines
13 KiB
Python

#!/usr/bin/env python3
import sys
import zcash
import zcash.rpc
from zcash.core import b2x, lx, x, COIN
from zcash.core import CMutableTransaction, CMutableTxOut, CMutableTxIn
from zcash.core.script import CScript, OP_DUP, OP_IF, OP_ELSE, OP_ENDIF
from zcash.core.script import OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG
from zcash.core.script import SignatureHash, SIGHASH_ALL, OP_FALSE, OP_DROP
from zcash.core.script import OP_CHECKLOCKTIMEVERIFY, OP_SHA256, OP_TRUE
from zcash.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH
from zcash.wallet import CBitcoinAddress, P2PKHBitcoinAddress
from zcash.wallet import P2SHBitcoinAddress
from xcat.utils import x2s
if sys.version_info.major < 3:
sys.stderr.write('Sorry, Python 3.x required by this example.\n')
sys.exit(1)
FEE = 0.001 * COIN
class zcashProxy():
def __init__(self, network='regtest', timeout=900):
if network not in ['testnet', 'mainnet', 'regtest']:
raise ValueError('Allowed networks are regtest, testnet, mainnet.')
if not isinstance(timeout, int) or timeout < 1:
raise ValueError('Timeout should be a positive integer.')
self.network = network
self.timeout = timeout
zcash.SelectParams(self.network)
self.zcashd = zcash.rpc.Proxy(timeout=self.timeout)
def validateaddress(self, addr):
return self.zcashd.validateaddress(addr)
def get_keys(self, funder_address, redeemer_address):
fundpubkey = CBitcoinAddress(funder_address)
redeempubkey = CBitcoinAddress(redeemer_address)
return fundpubkey, redeempubkey
def privkey(self, address):
self.zcashd.dumpprivkey(address)
def hashtimelockcontract(self, funder, redeemer, commitment, locktime):
funderAddr = CBitcoinAddress(funder)
redeemerAddr = CBitcoinAddress(redeemer)
if type(commitment) == str:
commitment = x(commitment)
else:
raise ValueError("Commitment was not a string: {0}".format(commitment))
blocknum = self.zcashd.getblockcount()
print("Current blocknum on Zcash: ", blocknum)
redeemblocknum = blocknum + locktime
print("Redeemblocknum on Zcash: ", redeemblocknum)
# can rm op_dup and op_hash160 if you replace addrs with pubkeys
# (as raw hex/bin data?)
# can rm last op_equalverify (for direct pubkey comparison)
zec_redeemScript = CScript([
OP_IF, OP_SHA256, commitment, 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)
print("p2sh computed: ", p2sh)
# Import address as soon as you create it
self.zcashd.importaddress(p2sh, "", False)
# Returning all this to be saved locally in p2sh.json
return {'p2sh': p2sh,
'redeemblocknum': redeemblocknum,
'redeemScript': b2x(zec_redeemScript),
'redeemer': redeemer,
'funder': funder,
'locktime': locktime}
def fund_htlc(self, p2sh, amount):
send_amount = float(amount) * COIN
# Import addr at same time as you fund
self.zcashd.importaddress(p2sh, "", False)
fund_txid = self.zcashd.sendtoaddress(p2sh, send_amount)
txid = b2x(lx(b2x(fund_txid)))
return txid
# Following two functions are about the same
def check_funds(self, p2sh):
self.zcashd.importaddress(p2sh, "", False)
# Get amount in address
amount = self.zcashd.getreceivedbyaddress(p2sh, 0)
amount = amount / COIN
return amount
def get_fund_status(self, p2sh):
self.zcashd.importaddress(p2sh, "", False)
amount = self.zcashd.getreceivedbyaddress(p2sh, 0)
amount = amount / COIN
print("Amount in zcash p2sh: ", amount, p2sh)
if amount > 0:
return 'funded'
else:
return 'empty'
def get_tx_details(self, txid):
fund_txinfo = self.zcashd.gettransaction(txid)
return fund_txinfo['details'][0]
def find_transaction_to_address(self, p2sh):
self.zcashd.importaddress(p2sh, "", False)
txs = self.zcashd.listunspent(0, 100)
for tx in txs:
if tx['address'] == CBitcoinAddress(p2sh):
print("Found tx to p2sh", p2sh)
return tx
def find_secret(self, p2sh, fundtx_input):
txs = self.zcashd.call('listtransactions', "*", 20, 0, True)
for tx in txs:
raw = self.zcashd.gettransaction(lx(tx['txid']))['hex']
decoded = self.zcashd.decoderawtransaction(raw)
# print("TXINFO", decoded['vin'][0])
if('txid' in decoded['vin'][0]):
sendid = decoded['vin'][0]['txid']
if (sendid == fundtx_input):
print("Found funding tx: ", sendid)
return self.parse_secret(lx(tx['txid']))
print("Redeem transaction with secret not found")
return
# def find_secret(self, p2sh, fundtx_input):
# print("In find secret zcashrpc")
# txs = self.zcashd.call('listtransactions', "*", 200, 0, True)
# for tx in txs:
# if tx['address'] == p2sh: # Only check txs involving imported p2sh
# raw = self.zcashd.gettransaction(lx(tx['txid']))['hex']
# decoded = self.zcashd.decoderawtransaction(raw)
# print('decoded', decoded)
# if('txid' in decoded['vin'][0]):
# sendid = decoded['vin'][0]['txid']
# print("sendid", sendid)
# if (sendid == fundtx_input ):
# print("Found funding zcash tx: ", sendid)
# res = self.parse_secret(lx(tx['txid']))
# secret = res[0]
# redeemPubkey = res[1]
# if secret is None:
# print("Secret not found")
# res = self.validateaddress(redeemPubkey)
# if res['ismine']:
# print("Funding tx already refunded. Sent to your address {0}".format(redeemPubkey))
# logging.debug("Redeem transaction with secret not found")
# return
def parse_secret(self, txid):
raw = self.zcashd.gettransaction(txid, True)['hex']
decoded = self.zcashd.decoderawtransaction(raw)
scriptSig = decoded['vin'][0]['scriptSig']
asm = scriptSig['asm'].split(" ")
pubkey = asm[1]
try:
secret = x2s(asm[2])
except:
secret = None
self.redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey))
print("redeemPubkey: ", self.redeemPubkey)
return secret
def redeem_contract(self, contract, secret):
# How to find redeemScript and redeemblocknum from blockchain?
p2sh = contract.p2sh
# checking there are funds in the address
amount = self.check_funds(p2sh)
if(amount == 0):
print("Address ", p2sh, " not funded")
quit()
fundtx = self.find_transaction_to_address(p2sh)
amount = fundtx['amount'] / COIN
# print("Found fund_tx: ", fundtx)
p2sh = P2SHBitcoinAddress(p2sh)
if fundtx['address'] == p2sh:
print("Found {0} in p2sh {1}, redeeming...".format(amount, p2sh))
# Where can you find redeemblocknum in the transaction?
# redeemblocknum = find_redeemblocknum(contract)
blockcount = self.zcashd.getblockcount()
print("\nCurrent blocknum at time of redeem on Zcash:", blockcount)
if blockcount < contract.redeemblocknum:
return self.redeem(contract, fundtx, secret)
else:
print("nLocktime exceeded, refunding")
return self.refund(contract)
else:
print("No contract for this p2sh found in database", p2sh)
def redeem(self, contract, fundtx, secret):
# TODO: parse the script once, up front.
redeemPubKey = self.find_redeemAddr(contract)
print('redeemPubKey', redeemPubKey)
zec_redeemScript = CScript(x(contract.redeemScript))
txin = CMutableTxIn(fundtx['outpoint'])
txout = CMutableTxOut(fundtx['amount'] - FEE,
redeemPubKey.to_scriptPubKey())
# Create the unsigned raw transaction.
tx = CMutableTransaction([txin], [txout])
sighash = SignatureHash(zec_redeemScript, tx, 0, SIGHASH_ALL)
# TODO: figure out how to better protect privkey
privkey = self.zcashd.dumpprivkey(redeemPubKey)
sig = privkey.sign(sighash) + bytes([SIGHASH_ALL])
print("SECRET", secret)
preimage = secret.encode('utf-8')
txin.scriptSig = CScript([sig, privkey.pub, preimage,
OP_TRUE, zec_redeemScript])
txin_scriptPubKey = zec_redeemScript.to_p2sh_scriptPubKey()
print('Raw redeem transaction hex: ', b2x(tx.serialize()))
VerifyScript(txin.scriptSig, txin_scriptPubKey,
tx, 0, (SCRIPT_VERIFY_P2SH,))
print("Script verified, sending raw redeem transaction...")
txid = self.zcashd.sendrawtransaction(tx)
redeem_tx = b2x(lx(b2x(txid)))
fund_tx = str(fundtx['outpoint'])
return {"redeem_tx": redeem_tx, "fund_tx": fund_tx}
def refund(self, contract):
fundtx = self.find_transaction_to_address(contract.p2sh)
print("Fund tx found in refund: ", fundtx)
refundPubKey = self.find_refundAddr(contract)
print('refundPubKey: {0}'.format(refundPubKey))
redeemScript = CScript(x(contract.redeemScript))
txin = CMutableTxIn(fundtx['outpoint'])
txout = CMutableTxOut(fundtx['amount'] - FEE,
refundPubKey.to_scriptPubKey())
# Create the unsigned raw transaction.
tx = CMutableTransaction([txin], [txout])
# Set nSequence and nLockTime
txin.nSequence = 0
tx.nLockTime = contract.redeemblocknum
# Create the unsigned raw transaction.
sighash = SignatureHash(redeemScript, tx, 0, SIGHASH_ALL)
privkey = self.zcashd.dumpprivkey(refundPubKey)
sig = privkey.sign(sighash) + bytes([SIGHASH_ALL])
# Sign without secret
txin.scriptSig = CScript([sig, privkey.pub, OP_FALSE, redeemScript])
txin_scriptPubKey = redeemScript.to_p2sh_scriptPubKey()
print('Raw redeem transaction hex: {0}'.format(b2x(tx.serialize())))
res = VerifyScript(txin.scriptSig, txin_scriptPubKey,
tx, 0, (SCRIPT_VERIFY_P2SH,))
print("Script verified, sending raw transaction... (NOT)", res)
txid = self.zcashd.sendrawtransaction(tx)
refund_tx = b2x(lx(b2x(txid)))
fund_tx = str(fundtx['outpoint'])
return {"refund_tx": refund_tx, "fund_tx": fund_tx}
def parse_script(self, script_hex):
redeemScript = self.zcashd.decodescript(script_hex)
scriptarray = redeemScript['asm'].split(' ')
return scriptarray
def find_redeemblocknum(self, contract):
print("In find_redeemblocknum")
scriptarray = self.parse_script(contract.redeemScript)
print("Returning scriptarray", scriptarray)
redeemblocknum = scriptarray[8]
return int(redeemblocknum)
def find_redeemAddr(self, contract):
scriptarray = self.parse_script(contract.redeemScript)
redeemer = scriptarray[6]
redeemAddr = P2PKHBitcoinAddress.from_bytes(x(redeemer))
return redeemAddr
def find_refundAddr(self, contract):
scriptarray = self.parse_script(contract.redeemScript)
funder = scriptarray[13]
refundAddr = P2PKHBitcoinAddress.from_bytes(x(funder))
return refundAddr
def find_recipient(self, contract):
# make this dependent on actual fund tx to p2sh, not contract
txid = contract.fund_tx
raw = self.zcashd.gettransaction(lx(txid), True)['hex']
decoded = self.zcashd.decoderawtransaction(raw)
scriptSig = decoded['vin'][0]['scriptSig']
print("Decoded", scriptSig)
asm = scriptSig['asm'].split(" ")
pubkey = asm[1]
initiator = CBitcoinAddress(contract.initiator)
fulfiller = CBitcoinAddress(contract.fulfiller)
print("Initiator", b2x(initiator))
print("Fulfiller", b2x(fulfiller))
print('pubkey', pubkey)
redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey))
print('redeemPubkey', redeemPubkey)
def new_zcash_addr(self):
addr = self.zcashd.getnewaddress()
return str(addr)
def generate(self, num):
blocks = self.zcashd.generate(num)
return blocks