zbxcat/xcat/zcashRPC.py

307 lines
13 KiB
Python

#!/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, P2SHBitcoinAddress, P2PKHBitcoinAddress
from xcat.utils import *
# SelectParams('testnet')
SelectParams('regtest')
zcashd = zcash.rpc.Proxy()
FEE = 0.001*COIN
def validateaddress(addr):
return zcashd.validateaddress(addr)
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, commitment, locktime):
funderAddr = CBitcoinAddress(funder)
redeemerAddr = CBitcoinAddress(redeemer)
if type(commitment) == str:
commitment = x(commitment)
# h = sha256(secret)
blocknum = zcashd.getblockcount()
print("Current blocknum", blocknum)
redeemblocknum = blocknum + locktime
print("REDEEMBLOCKNUM ZCASH", redeemblocknum)
print("COMMITMENT on zxcat", commitment)
# can rm op_dup and op_hash160 if you replace addrs with pubkeys (as raw hex/bin data?), and 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)
# 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(p2sh, amount):
send_amount = float(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 find_transaction_to_address(p2sh):
zcashd.importaddress(p2sh, "", False)
txs = zcashd.listunspent(0, 100)
for tx in txs:
# print("tx addr:", tx['address'])
# print(type(tx['address']))
# print(type(p2sh))
if tx['address'] == CBitcoinAddress(p2sh):
print("Found tx to p2sh", p2sh)
print(tx)
return tx
# def get_tx_details(txid):
# # This method is problematic I haven't gotten the type conversions right
# print(bytearray.fromhex(txid))
# print(b2x(bytearray.fromhex(txid)))
# fund_txinfo = zcashd.gettransaction(bytearray.fromhex(txid))
# print(fund_txinfo)
#
# return fund_txinfo['details'][0]
def find_secret(p2sh):
return parse_secret('4c25b5db9f3df48e48306891d8437c69308afa122f92416df1a3ba0d3604882f')
zcashd.importaddress(p2sh, "", False)
# is this working?
txs = zcashd.listtransactions()
for tx in txs:
# print("tx addr:", tx['address'])
# print(type(tx['address']))
# print(type(p2sh))
if (tx['address'] == p2sh ) and (tx['category'] == "send"):
print(type(tx['txid']))
print(str.encode(tx['txid']))
raw = zcashd.getrawtransaction(lx(tx['txid']),True)['hex']
decoded = zcashd.decoderawtransaction(raw)
print("deo:", decoded['vin'][0]['scriptSig']['asm'])
def parse_secret(txid):
raw = zcashd.gettransaction(lx(txid), True)['hex']
# print("Raw", raw)
decoded = zcashd.decoderawtransaction(raw)
scriptSig = decoded['vin'][0]['scriptSig']
print("Decoded", scriptSig)
asm = scriptSig['asm'].split(" ")
pubkey = asm[1]
secret = x2s(asm[2])
redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey))
print('redeemPubkey', redeemPubkey)
print(secret)
return secret
# redeems automatically after buyer has funded tx, by scanning for transaction to the p2sh
# i.e., doesn't require buyer telling us fund txid
def auto_redeem(contract, secret):
# How to find redeemScript and redeemblocknum from blockchain?
print("Contract in auto redeem", contract.__dict__)
p2sh = contract.p2sh
#checking there are funds in the address
amount = check_funds(p2sh)
if(amount == 0):
print("address ", p2sh, " not funded")
quit()
fundtx = find_transaction_to_address(p2sh)
amount = fundtx['amount'] / COIN
print("Found fundtx:", 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 = zcashd.getblockcount()
print("\nCurrent blocknum at time of redeem on Zcash:", blockcount)
if blockcount < redeemblocknum:
redeemPubKey = 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 = 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])
print("txin.scriptSig", b2x(txin.scriptSig))
txin_scriptPubKey = zec_redeemScript.to_p2sh_scriptPubKey()
print('Redeem txhex', b2x(tx.serialize()))
VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,))
print("script verified, sending raw tx")
txid = zcashd.sendrawtransaction(tx)
print("Txid of submitted redeem tx: ", b2x(lx(b2x(txid))))
print("TXID SUCCESSFULLY REDEEMED")
return 'redeem_tx', b2x(lx(b2x(txid)))
else:
# if blockcount >= redeemblocknum:
# tx.nLockTime = redeemblocknum
print("nLocktime exceeded, refunding")
refundPubKey = find_refundAddr(contract)
print('refundPubKey', refundPubKey)
txid = zcashd.sendtoaddress(refundPubKey, fundtx['amount'] - FEE)
print("Txid of refund tx:", b2x(lx(b2x(txid))))
print("TXID SUCCESSFULLY REFUNDED")
return 'refund_tx', b2x(lx(b2x(txid)))
else:
print("No contract for this p2sh found in database", p2sh)
def redeem_contract(contract, secret):
# How to find redeemScript and redeemblocknum from blockchain?
print("Contract in redeem contract", contract.__dict__)
p2sh = contract.p2sh
#checking there are funds in the address
amount = check_funds(p2sh)
if(amount == 0):
print("address ", p2sh, " not funded")
quit()
fundtx = find_transaction_to_address(p2sh)
amount = fundtx['amount'] / COIN
print("Found fundtx:", 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 = zcashd.getblockcount()
print("\nCurrent blocknum at time of redeem on Zcash:", blockcount)
if blockcount < contract.redeemblocknum:
# TODO: parse the script once, up front.
redeemPubKey = 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 = 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])
print("txin.scriptSig", b2x(txin.scriptSig))
txin_scriptPubKey = zec_redeemScript.to_p2sh_scriptPubKey()
print('Redeem txhex', b2x(tx.serialize()))
VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,))
print("script verified, sending raw tx")
txid = zcashd.sendrawtransaction(tx)
print("Txid of submitted redeem tx: ", b2x(lx(b2x(txid))))
print("TXID SUCCESSFULLY REDEEMED")
return 'redeem_tx', b2x(lx(b2x(txid)))
else:
print("nLocktime exceeded, refunding")
refundPubKey = find_refundAddr(contract)
print('refundPubKey', refundPubKey)
txid = zcashd.sendtoaddress(refundPubKey, fundtx['amount'] - FEE)
print("Txid of refund tx:", b2x(lx(b2x(txid))))
print("TXID SUCCESSFULLY REFUNDED")
return 'refund_tx', b2x(lx(b2x(txid)))
else:
print("No contract for this p2sh found in database", p2sh)
def parse_script(script_hex):
redeemScript = zcashd.decodescript(script_hex)
scriptarray = redeemScript['asm'].split(' ')
return scriptarray
def find_redeemblocknum(contract):
print("In find_redeemblocknum")
scriptarray = parse_script(contract.redeemScript)
print("Returning scriptarray", scriptarray)
redeemblocknum = scriptarray[8]
return int(redeemblocknum)
def find_redeemAddr(contract):
scriptarray = parse_script(contract.redeemScript)
redeemer = scriptarray[6]
redeemAddr = P2PKHBitcoinAddress.from_bytes(x(redeemer))
return redeemAddr
def find_refundAddr(contract):
scriptarray = parse_script(contract.redeemScript)
funder = scriptarray[13]
refundAddr = P2PKHBitcoinAddress.from_bytes(x(funder))
return refundAddr
def find_recipient(contract):
# make this dependent on actual fund tx to p2sh, not contract
txid = contract.fund_tx
raw = zcashd.gettransaction(lx(txid), True)['hex']
# print("Raw", raw)
decoded = 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("Fulfiler", b2x(fulfiller))
print('pubkey', pubkey)
redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey))
print('redeemPubkey', redeemPubkey)
# addr = CBitcoinAddress('tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ')
# print(addr)
# # print(b2x('tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ'))
# print(b2x(addr))
def new_zcash_addr():
addr = zcashd.getnewaddress()
print('new ZEC addr', addr.to_p2sh_scriptPubKey)
return addr.to_scriptPubKey()
def generate(num):
blocks = zcashd.generate(num)
return blocks