2017-05-22 18:00:34 -07:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
import sys
|
|
|
|
import bitcoin
|
|
|
|
import bitcoin.rpc
|
2017-09-12 21:16:42 -07:00
|
|
|
# from bitcoin import SelectParams
|
2017-09-12 22:44:25 -07:00
|
|
|
from bitcoin.core import b2x, lx, x, COIN, CMutableTxOut
|
|
|
|
from bitcoin.core import CMutableTxIn, CMutableTransaction
|
|
|
|
from bitcoin.core.script import CScript, OP_DUP, OP_IF, OP_ELSE, OP_ENDIF
|
|
|
|
from bitcoin.core.script import OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG
|
|
|
|
from bitcoin.core.script import SignatureHash, SIGHASH_ALL, OP_FALSE, OP_DROP
|
|
|
|
from bitcoin.core.script import OP_CHECKLOCKTIMEVERIFY, OP_SHA256, OP_TRUE
|
2017-05-22 18:00:34 -07:00
|
|
|
from bitcoin.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH
|
2017-09-12 22:44:25 -07:00
|
|
|
from bitcoin.wallet import CBitcoinAddress, P2SHBitcoinAddress
|
|
|
|
from bitcoin.wallet import P2PKHBitcoinAddress
|
2017-05-22 18:00:34 -07:00
|
|
|
|
2017-09-12 22:44:25 -07:00
|
|
|
import xcat.utils as utils
|
2017-08-29 17:17:28 -07:00
|
|
|
import logging
|
2017-07-28 13:57:44 -07:00
|
|
|
|
2017-09-12 21:16:42 -07:00
|
|
|
if sys.version_info.major < 3:
|
|
|
|
sys.stderr.write('Sorry, Python 3.x required by this example.\n')
|
|
|
|
sys.exit(1)
|
|
|
|
|
2017-09-13 08:35:31 -07:00
|
|
|
FEE = 0.001 * COIN
|
2017-05-22 18:00:34 -07:00
|
|
|
|
2017-09-12 21:16:42 -07:00
|
|
|
|
2017-08-25 14:36:42 -07:00
|
|
|
class bitcoinProxy():
|
2017-08-25 15:22:38 -07:00
|
|
|
def __init__(self, network='regtest', timeout=900):
|
2017-08-29 17:17:28 -07:00
|
|
|
if network is not 'testnet' and network is not 'mainnet':
|
2017-09-12 21:16:42 -07:00
|
|
|
network = 'regtest'
|
2017-08-29 17:17:28 -07:00
|
|
|
logging.debug("NETWORK in proxy: {0}".format(network))
|
2017-08-25 14:36:42 -07:00
|
|
|
self.network = network
|
|
|
|
self.timeout = timeout
|
|
|
|
|
2017-09-12 21:16:42 -07:00
|
|
|
bitcoin.SelectParams(self.network)
|
2017-08-25 14:36:42 -07:00
|
|
|
self.bitcoind = bitcoin.rpc.Proxy(timeout=self.timeout)
|
|
|
|
|
|
|
|
def validateaddress(self, addr):
|
|
|
|
return self.bitcoind.validateaddress(addr)
|
|
|
|
|
|
|
|
def find_secret(self, p2sh, fundtx_input):
|
|
|
|
txs = self.bitcoind.call('listtransactions', "*", 20, 0, True)
|
|
|
|
for tx in txs:
|
|
|
|
raw = self.bitcoind.gettransaction(lx(tx['txid']))['hex']
|
|
|
|
decoded = self.bitcoind.decoderawtransaction(raw)
|
|
|
|
print("TXINFO", decoded['vin'][0])
|
|
|
|
if('txid' in decoded['vin'][0]):
|
|
|
|
sendid = decoded['vin'][0]['txid']
|
2017-09-12 21:16:42 -07:00
|
|
|
if (sendid == fundtx_input):
|
2017-08-25 14:36:42 -07:00
|
|
|
print("Found funding tx: ", sendid)
|
2017-09-12 21:16:42 -07:00
|
|
|
return self.parse_secret(lx(tx['txid']))
|
2017-08-25 14:36:42 -07:00
|
|
|
print("Redeem transaction with secret not found")
|
|
|
|
return
|
|
|
|
|
|
|
|
def parse_secret(self, txid):
|
|
|
|
raw = zcashd.gettransaction(txid, True)['hex']
|
|
|
|
decoded = zcashd.call('decoderawtransaction', raw)
|
|
|
|
scriptSig = decoded['vin'][0]['scriptSig']
|
|
|
|
asm = scriptSig['asm'].split(" ")
|
2017-09-12 22:44:25 -07:00
|
|
|
# pubkey = asm[1]
|
|
|
|
secret = utils.x2s(asm[2])
|
|
|
|
# redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey))
|
2017-08-25 14:36:42 -07:00
|
|
|
return secret
|
|
|
|
|
|
|
|
def get_keys(self, funder_address, redeemer_address):
|
|
|
|
fundpubkey = CBitcoinAddress(funder_address)
|
|
|
|
redeempubkey = CBitcoinAddress(redeemer_address)
|
|
|
|
# fundpubkey = self.bitcoind.getnewaddress()
|
|
|
|
# redeempubkey = self.bitcoind.getnewaddress()
|
|
|
|
return fundpubkey, redeempubkey
|
|
|
|
|
|
|
|
def privkey(self, address):
|
|
|
|
self.bitcoind.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.bitcoind.getblockcount()
|
|
|
|
print("Current blocknum on Bitcoin: ", blocknum)
|
|
|
|
redeemblocknum = blocknum + locktime
|
|
|
|
print("Redeemblocknum on Bitcoin: ", redeemblocknum)
|
2017-09-12 22:48:56 -07:00
|
|
|
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])
|
2017-09-12 22:44:25 -07:00
|
|
|
# print("Redeem script for p2sh contract on Bitcoin blockchain: "
|
|
|
|
# "{0}".format(b2x(redeemScript)))
|
2017-08-25 14:36:42 -07:00
|
|
|
txin_scriptPubKey = redeemScript.to_p2sh_scriptPubKey()
|
|
|
|
# Convert the P2SH scriptPubKey to a base58 Bitcoin address
|
2017-09-12 22:44:25 -07:00
|
|
|
txin_p2sh_address = CBitcoinAddress.from_scriptPubKey(
|
|
|
|
txin_scriptPubKey)
|
2017-08-25 14:36:42 -07:00
|
|
|
p2sh = str(txin_p2sh_address)
|
|
|
|
# Import address at same time you create
|
|
|
|
self.bitcoind.importaddress(p2sh, "", False)
|
|
|
|
print("p2sh computed", p2sh)
|
2017-09-12 22:44:25 -07:00
|
|
|
return {'p2sh': p2sh,
|
|
|
|
'redeemblocknum': redeemblocknum,
|
|
|
|
'redeemScript': b2x(redeemScript),
|
|
|
|
'redeemer': redeemer,
|
|
|
|
'funder': funder,
|
|
|
|
'locktime': locktime}
|
2017-08-25 14:36:42 -07:00
|
|
|
|
|
|
|
def fund_htlc(self, p2sh, amount):
|
|
|
|
send_amount = float(amount) * COIN
|
|
|
|
# Import address at same time that you fund it
|
|
|
|
self.bitcoind.importaddress(p2sh, "", False)
|
|
|
|
fund_txid = self.bitcoind.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.bitcoind.importaddress(p2sh, "", False)
|
|
|
|
# Get amount in address
|
|
|
|
amount = self.bitcoind.getreceivedbyaddress(p2sh, 0)
|
2017-09-13 08:35:31 -07:00
|
|
|
amount = amount / COIN
|
2017-08-25 14:36:42 -07:00
|
|
|
return amount
|
|
|
|
|
|
|
|
def get_fund_status(self, p2sh):
|
|
|
|
self.bitcoind.importaddress(p2sh, "", False)
|
|
|
|
amount = self.bitcoind.getreceivedbyaddress(p2sh, 0)
|
2017-09-13 08:35:31 -07:00
|
|
|
amount = amount / COIN
|
2017-08-25 14:36:42 -07:00
|
|
|
print("Amount in bitcoin p2sh: ", amount, p2sh)
|
|
|
|
if amount > 0:
|
|
|
|
return 'funded'
|
|
|
|
else:
|
|
|
|
return 'empty'
|
|
|
|
|
2017-09-12 22:44:25 -07:00
|
|
|
# TODO: FIX search for p2sh in block
|
2017-08-25 14:36:42 -07:00
|
|
|
def search_p2sh(self, block, p2sh):
|
|
|
|
print("Fetching block...")
|
|
|
|
blockdata = self.bitcoind.getblock(lx(block))
|
|
|
|
print("done fetching block")
|
|
|
|
txs = blockdata.vtx
|
|
|
|
print("txs", txs)
|
|
|
|
for tx in txs:
|
|
|
|
txhex = b2x(tx.serialize())
|
|
|
|
# Using my fork of python-zcashlib to get result of decoderawtransaction
|
|
|
|
txhex = txhex + '00'
|
|
|
|
rawtx = zcashd.decoderawtransaction(txhex)
|
|
|
|
# print('rawtx', rawtx)
|
|
|
|
print(rawtx)
|
|
|
|
for vout in rawtx['vout']:
|
|
|
|
if 'addresses' in vout['scriptPubKey']:
|
|
|
|
for addr in vout['scriptPubKey']['addresses']:
|
|
|
|
print("Sent to address:", addr)
|
|
|
|
if addr == p2sh:
|
2017-09-12 22:44:25 -07:00
|
|
|
print("Address to p2sh found in transaction! ",
|
|
|
|
addr)
|
2017-08-25 14:36:42 -07:00
|
|
|
print("Returning from search_p2sh")
|
|
|
|
|
|
|
|
def get_tx_details(self, txid):
|
|
|
|
# must convert txid string to bytes x(txid)
|
|
|
|
fund_txinfo = self.bitcoind.gettransaction(lx(txid))
|
|
|
|
return fund_txinfo['details'][0]
|
|
|
|
|
|
|
|
def redeem_contract(self, contract, secret):
|
|
|
|
print("Parsing script for redeem_contract...")
|
2017-08-25 15:22:38 -07:00
|
|
|
scriptarray = self.parse_script(contract.redeemScript)
|
2017-08-25 14:36:42 -07:00
|
|
|
redeemblocknum = scriptarray[8]
|
2017-09-08 14:24:12 -07:00
|
|
|
self.redeemPubKey = P2PKHBitcoinAddress.from_bytes(x(scriptarray[6]))
|
2017-09-12 22:44:25 -07:00
|
|
|
# refundPubKey = P2PKHBitcoinAddress.from_bytes(x(scriptarray[13]))
|
2017-08-25 14:36:42 -07:00
|
|
|
p2sh = contract.p2sh
|
2017-09-12 22:44:25 -07:00
|
|
|
# checking there are funds in the address
|
2017-08-25 15:22:38 -07:00
|
|
|
amount = self.check_funds(p2sh)
|
2017-08-25 14:36:42 -07:00
|
|
|
if(amount == 0):
|
|
|
|
print("address ", p2sh, " not funded")
|
|
|
|
quit()
|
2017-08-25 15:22:38 -07:00
|
|
|
fundtx = self.find_transaction_to_address(p2sh)
|
2017-08-25 14:36:42 -07:00
|
|
|
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))
|
|
|
|
|
|
|
|
blockcount = self.bitcoind.getblockcount()
|
|
|
|
print("\nCurrent blocknum at time of redeem on Zcash:", blockcount)
|
|
|
|
if blockcount < int(redeemblocknum):
|
2017-08-30 12:14:32 -07:00
|
|
|
return self.redeem(contract, fundtx, secret)
|
2017-08-25 14:36:42 -07:00
|
|
|
else:
|
|
|
|
print("nLocktime exceeded, refunding")
|
2017-08-30 12:14:32 -07:00
|
|
|
return self.refund(contract)
|
2017-07-26 13:23:12 -07:00
|
|
|
else:
|
2017-08-25 14:36:42 -07:00
|
|
|
print("No contract for this p2sh found in database", p2sh)
|
|
|
|
|
2017-09-08 14:24:12 -07:00
|
|
|
def redeem(self, contract, fundtx, secret):
|
|
|
|
print('redeemPubKey', self.redeemPubKey)
|
|
|
|
# TODO: Compare with script on blockchain?
|
|
|
|
redeemScript = CScript(x(contract.redeemScript))
|
2017-08-30 12:14:32 -07:00
|
|
|
txin = CMutableTxIn(fundtx['outpoint'])
|
2017-09-12 22:44:25 -07:00
|
|
|
txout = CMutableTxOut(fundtx['amount'] - FEE,
|
|
|
|
self.redeemPubKey.to_scriptPubKey())
|
|
|
|
|
2017-08-30 12:14:32 -07:00
|
|
|
# Create the unsigned raw transaction.
|
|
|
|
tx = CMutableTransaction([txin], [txout])
|
2017-09-08 14:24:12 -07:00
|
|
|
sighash = SignatureHash(redeemScript, tx, 0, SIGHASH_ALL)
|
2017-08-30 12:14:32 -07:00
|
|
|
# TODO: protect privkey better, separate signing from rawtx creation
|
2017-09-08 14:24:12 -07:00
|
|
|
privkey = self.bitcoind.dumpprivkey(self.redeemPubKey)
|
2017-08-30 12:14:32 -07:00
|
|
|
sig = privkey.sign(sighash) + bytes([SIGHASH_ALL])
|
|
|
|
preimage = secret.encode('utf-8')
|
2017-09-12 22:44:25 -07:00
|
|
|
txin.scriptSig = CScript([sig, privkey.pub, preimage,
|
|
|
|
OP_TRUE, redeemScript])
|
2017-08-30 12:14:32 -07:00
|
|
|
|
|
|
|
# print("txin.scriptSig", b2x(txin.scriptSig))
|
2017-09-08 14:24:12 -07:00
|
|
|
txin_scriptPubKey = redeemScript.to_p2sh_scriptPubKey()
|
2017-08-30 12:14:32 -07:00
|
|
|
print('Raw redeem transaction hex: ', b2x(tx.serialize()))
|
2017-09-12 22:44:25 -07:00
|
|
|
VerifyScript(txin.scriptSig, txin_scriptPubKey,
|
|
|
|
tx, 0, (SCRIPT_VERIFY_P2SH,))
|
2017-08-30 12:14:32 -07:00
|
|
|
print("Script verified, sending raw transaction...")
|
|
|
|
txid = self.bitcoind.sendrawtransaction(tx)
|
|
|
|
fund_tx = str(fundtx['outpoint'])
|
2017-09-12 22:44:25 -07:00
|
|
|
redeem_tx = b2x(lx(b2x(txid)))
|
|
|
|
return {"redeem_tx": redeem_tx, "fund_tx": fund_tx}
|
2017-08-30 12:14:32 -07:00
|
|
|
|
|
|
|
def refund(self, contract):
|
|
|
|
fundtx = self.find_transaction_to_address(contract.p2sh)
|
2017-09-08 14:24:12 -07:00
|
|
|
print("Fund tx found in refund: ", fundtx)
|
2017-08-30 12:14:32 -07:00
|
|
|
refundPubKey = self.find_refundAddr(contract)
|
2017-09-08 14:24:12 -07:00
|
|
|
print('refundPubKey: {0}'.format(refundPubKey))
|
|
|
|
|
|
|
|
redeemScript = CScript(x(contract.redeemScript))
|
|
|
|
txin = CMutableTxIn(fundtx['outpoint'])
|
2017-09-12 22:44:25 -07:00
|
|
|
txout = CMutableTxOut(fundtx['amount'] - FEE,
|
|
|
|
refundPubKey.to_scriptPubKey())
|
|
|
|
|
2017-09-08 14:24:12 -07:00
|
|
|
# Create the unsigned raw transaction.
|
|
|
|
tx = CMutableTransaction([txin], [txout])
|
|
|
|
sighash = SignatureHash(redeemScript, tx, 0, SIGHASH_ALL)
|
|
|
|
privkey = self.bitcoind.dumpprivkey(refundPubKey)
|
|
|
|
sig = privkey.sign(sighash) + bytes([SIGHASH_ALL])
|
2017-09-12 22:44:25 -07:00
|
|
|
|
2017-09-08 14:24:12 -07:00
|
|
|
# Sign without secret
|
|
|
|
txin.scriptSig = CScript([sig, privkey.pub, OP_FALSE, redeemScript])
|
2017-09-12 22:44:25 -07:00
|
|
|
|
2017-09-08 14:24:12 -07:00
|
|
|
# txin.nSequence = 2185
|
|
|
|
txin_scriptPubKey = redeemScript.to_p2sh_scriptPubKey()
|
|
|
|
print('Raw redeem transaction hex: {0}'.format(b2x(tx.serialize())))
|
2017-09-12 22:44:25 -07:00
|
|
|
res = VerifyScript(txin.scriptSig, txin_scriptPubKey,
|
|
|
|
tx, 0, (SCRIPT_VERIFY_P2SH,))
|
2017-09-08 14:24:12 -07:00
|
|
|
print("Script verified, sending raw transaction... (NOT)", res)
|
|
|
|
txid = self.bitcoind.sendrawtransaction(tx)
|
2017-09-12 22:44:25 -07:00
|
|
|
refund_tx = b2x(lx(b2x(txid)))
|
2017-08-30 12:14:32 -07:00
|
|
|
fund_tx = str(fundtx['outpoint'])
|
|
|
|
return {"refund_tx": refund_tx, "fund_tx": fund_tx}
|
|
|
|
|
2017-08-25 15:22:38 -07:00
|
|
|
def parse_script(self, script_hex):
|
|
|
|
redeemScript = self.bitcoind.call('decodescript', script_hex)
|
|
|
|
scriptarray = redeemScript['asm'].split(' ')
|
|
|
|
return scriptarray
|
|
|
|
|
2017-08-25 14:36:42 -07:00
|
|
|
def find_redeemblocknum(self, contract):
|
2017-09-12 22:44:25 -07:00
|
|
|
scriptarray = self.parse_script(contract.redeemScript)
|
2017-08-25 14:36:42 -07:00
|
|
|
redeemblocknum = scriptarray[8]
|
|
|
|
return int(redeemblocknum)
|
|
|
|
|
|
|
|
def find_redeemAddr(self, contract):
|
2017-09-12 22:44:25 -07:00
|
|
|
scriptarray = self.parse_script(contract.redeemScript)
|
2017-08-25 14:36:42 -07:00
|
|
|
redeemer = scriptarray[6]
|
|
|
|
redeemAddr = P2PKHBitcoinAddress.from_bytes(x(redeemer))
|
|
|
|
return redeemAddr
|
|
|
|
|
|
|
|
def find_refundAddr(self, contract):
|
2017-08-30 12:14:32 -07:00
|
|
|
scriptarray = self.parse_script(contract.redeemScript)
|
2017-08-25 14:36:42 -07:00
|
|
|
funder = scriptarray[13]
|
|
|
|
refundAddr = P2PKHBitcoinAddress.from_bytes(x(funder))
|
|
|
|
return refundAddr
|
|
|
|
|
|
|
|
def find_transaction_to_address(self, p2sh):
|
|
|
|
self.bitcoind.importaddress(p2sh, "", False)
|
|
|
|
txs = self.bitcoind.listunspent()
|
|
|
|
for tx in txs:
|
|
|
|
if tx['address'] == CBitcoinAddress(p2sh):
|
2017-08-30 12:14:32 -07:00
|
|
|
logging.debug("Found tx to p2sh: {0}".format(p2sh))
|
2017-08-25 14:36:42 -07:00
|
|
|
return tx
|
|
|
|
|
|
|
|
def new_bitcoin_addr(self):
|
|
|
|
addr = self.bitcoind.getnewaddress()
|
|
|
|
return str(addr)
|
|
|
|
|
|
|
|
def generate(self, num):
|
|
|
|
blocks = self.bitcoind.generate(num)
|
|
|
|
return blocks
|