#!/usr/bin/env python3 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 x2s FEE = 0.001*COIN class zcashProxy(): def __init__(self, network='regtest', timeout=900): self.network = network self.timeout = timeout 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) # h = sha256(secret) 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?), 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) # 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) 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 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] secret = x2s(asm[2]) redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey)) 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: # 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} else: print("nLocktime exceeded, refunding") refundPubKey = self.find_refundAddr(contract) print('refundPubKey', refundPubKey) txid = self.zcashd.sendtoaddress(refundPubKey, fundtx['amount'] - FEE) refund_tx = b2x(lx(b2x(txid))) fund_tx = str(fundtx['outpoint']) return {"refund_tx": refund_tx, "fund_tx": fund_tx} else: print("No contract for this p2sh found in database", p2sh) 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