Merge branch 'firo' into GUI

This commit is contained in:
tecnovert
2022-11-10 23:51:36 +02:00
39 changed files with 1822 additions and 722 deletions

View File

@@ -1,3 +1,3 @@
name = "basicswap"
__version__ = "0.11.44"
__version__ = "0.11.45"

View File

@@ -15,7 +15,6 @@ import traceback
import subprocess
import basicswap.config as cfg
import basicswap.contrib.segwit_addr as segwit_addr
from .rpc import (
callrpc,
@@ -112,12 +111,6 @@ class BaseApp:
return c
raise ValueError('Unknown coin: {}'.format(coin_name))
def encodeSegwit(self, coin_type, raw):
return segwit_addr.encode(chainparams[coin_type][self.chain]['hrp'], 0, raw)
def decodeSegwit(self, coin_type, addr):
return bytes(segwit_addr.decode(chainparams[coin_type][self.chain]['hrp'], addr)[1])
def callrpc(self, method, params=[], wallet=None):
cc = self.coin_clients[Coins.PART]
return callrpc(cc['rpcport'], cc['rpcauth'], method, params, wallet, cc['rpchost'])

View File

@@ -34,6 +34,7 @@ from .interface.nmc import NMCInterface
from .interface.xmr import XMRInterface
from .interface.pivx import PIVXInterface
from .interface.dash import DASHInterface
from .interface.firo import FIROInterface
from .interface.passthrough_btc import PassthroughBTCInterface
from . import __version__
@@ -58,7 +59,6 @@ from .util.address import (
getKeyID,
decodeWif,
decodeAddress,
encodeAddress,
pubkeyToAddress,
)
from .chainparams import (
@@ -531,6 +531,8 @@ class BasicSwap(BaseApp):
return PIVXInterface(self.coin_clients[coin], self.chain, self)
elif coin == Coins.DASH:
return DASHInterface(self.coin_clients[coin], self.chain, self)
elif coin == Coins.FIRO:
return FIROInterface(self.coin_clients[coin], self.chain, self)
else:
raise ValueError('Unknown coin type')
@@ -549,7 +551,7 @@ class BasicSwap(BaseApp):
authcookiepath = os.path.join(self.getChainDatadirPath(coin), '.cookie')
pidfilename = cc['name']
if cc['name'] in ('bitcoin', 'litecoin', 'namecoin', 'dash'):
if cc['name'] in ('bitcoin', 'litecoin', 'namecoin', 'dash', 'firo'):
pidfilename += 'd'
pidfilepath = os.path.join(self.getChainDatadirPath(coin), pidfilename + '.pid')
@@ -725,7 +727,7 @@ class BasicSwap(BaseApp):
return
root_key = self.getWalletKey(coin_type, 1)
root_hash = ci.getAddressHashFromKey(root_key)[::-1]
root_hash = ci.getSeedHash(root_key)
try:
ci.initialiseWallet(root_key)
@@ -975,10 +977,8 @@ class BasicSwap(BaseApp):
raise ValueError('Invalid swap type for PART_ANON')
if (coin_from == Coins.PART_BLIND or coin_to == Coins.PART_BLIND) and swap_type != SwapTypes.XMR_SWAP:
raise ValueError('Invalid swap type for PART_BLIND')
if coin_from == Coins.PIVX and swap_type == SwapTypes.XMR_SWAP:
raise ValueError('TODO: PIVX -> XMR')
if coin_from == Coins.DASH and swap_type == SwapTypes.XMR_SWAP:
raise ValueError('TODO: DASH -> XMR')
if coin_from in (Coins.PIVX, Coins.DASH, Coins.FIRO, Coins.NMC) and swap_type == SwapTypes.XMR_SWAP:
raise ValueError('TODO: {} -> XMR'.format(coin_from.name))
def notify(self, event_type, event_data, session=None):
@@ -1053,7 +1053,6 @@ class BasicSwap(BaseApp):
ensure(amount_to < ci_to.max_amount(), 'To amount above max value for chain')
def validateOfferLockValue(self, coin_from, coin_to, lock_type, lock_value):
coin_from_has_csv = self.coin_clients[coin_from]['use_csv']
coin_to_has_csv = self.coin_clients[coin_to]['use_csv']
@@ -1609,18 +1608,6 @@ class BasicSwap(BaseApp):
self.mxDB.release()
return self._contract_count
def getUnspentsByAddr(self, coin_type):
ci = self.ci(coin_type)
unspent_addr = dict()
unspent = self.callcoinrpc(coin_type, 'listunspent')
for u in unspent:
if u['spendable'] is not True:
continue
unspent_addr[u['address']] = unspent_addr.get(u['address'], 0) + ci.make_int(u['amount'], r=1)
return unspent_addr
def getProofOfFunds(self, coin_type, amount_for, extra_commit_bytes):
ci = self.ci(coin_type)
self.log.debug('getProofOfFunds %s %s', ci.coin_name(), ci.format_amount(amount_for))
@@ -1628,28 +1615,7 @@ class BasicSwap(BaseApp):
if self.coin_clients[coin_type]['connection_type'] != 'rpc':
return (None, None)
# TODO: Lock unspent and use same output/s to fund bid
unspent_addr = self.getUnspentsByAddr(coin_type)
sign_for_addr = None
for addr, value in unspent_addr.items():
if value >= amount_for:
sign_for_addr = addr
break
ensure(sign_for_addr is not None, 'Could not find address with enough funds for proof')
self.log.debug('sign_for_addr %s', sign_for_addr)
if self.coin_clients[coin_type]['use_segwit']: # TODO: Use isSegwitAddress when scantxoutset can use combo
# 'Address does not refer to key' for non p2pkh
addrinfo = self.callcoinrpc(coin_type, 'getaddressinfo', [sign_for_addr])
pkh = addrinfo['scriptPubKey'][4:]
sign_for_addr = encodeAddress(bytes((chainparams[coin_type][self.chain]['pubkey_address'],)) + bytes.fromhex(pkh))
self.log.debug('sign_for_addr converted %s', sign_for_addr)
signature = self.callcoinrpc(coin_type, 'signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + extra_commit_bytes.hex()])
return (sign_for_addr, signature)
return ci.getProofOfFunds(amount_for, extra_commit_bytes)
def saveBidInSession(self, bid_id, bid, session, xmr_swap=None, save_in_progress=None):
session.add(bid)
@@ -1859,6 +1825,15 @@ class BasicSwap(BaseApp):
session.remove()
self.mxDB.release()
def setTxBlockInfoFromHeight(self, ci, tx, height):
try:
tx.block_height = height
block_header = ci.getBlockHeaderFromHeight(height)
tx.block_hash = bytes.fromhex(block_header['hash'])
tx.block_time = block_header['time'] # Or median_time?
except Exception as e:
self.log.warning(f'setTxBlockInfoFromHeight failed {e}')
def loadBidTxns(self, bid, session):
bid.txns = {}
for stx in session.query(SwapTx).filter(sa.and_(SwapTx.bid_id == bid.bid_id)):
@@ -2065,6 +2040,7 @@ class BasicSwap(BaseApp):
script=script,
)
bid.setITxState(TxStates.TX_SENT)
self.logEvent(Concepts.BID, bid.bid_id, EventLogTypes.ITX_PUBLISHED, '', None)
# Check non-bip68 final
try:
@@ -2293,16 +2269,16 @@ class BasicSwap(BaseApp):
xmr_swap.kbsl_dleag = xmr_swap.pkbsl
# MSG2F
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script = ci_from.createScriptLockTx(
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script = ci_from.createSCLockTx(
bid.amount,
xmr_swap.pkal, xmr_swap.pkaf, xmr_swap.vkbv
)
xmr_swap.a_lock_tx = ci_from.fundScriptLockTx(xmr_swap.a_lock_tx, xmr_offer.a_fee_rate, xmr_swap.vkbv)
xmr_swap.a_lock_tx = ci_from.fundSCLockTx(xmr_swap.a_lock_tx, xmr_offer.a_fee_rate, xmr_swap.vkbv)
xmr_swap.a_lock_tx_id = ci_from.getTxid(xmr_swap.a_lock_tx)
a_lock_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_tx_script)
xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script, xmr_swap.a_swap_refund_value = ci_from.createScriptLockRefundTx(
xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script, xmr_swap.a_swap_refund_value = ci_from.createSCLockRefundTx(
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script,
xmr_swap.pkal, xmr_swap.pkaf,
xmr_offer.lock_time_1, xmr_offer.lock_time_2,
@@ -2316,7 +2292,7 @@ class BasicSwap(BaseApp):
ensure(v, 'Invalid coin A lock refund tx leader sig')
pkh_refund_to = ci_from.decodeAddress(self.getReceiveAddressForCoin(coin_from))
xmr_swap.a_lock_refund_spend_tx = ci_from.createScriptLockRefundSpendTx(
xmr_swap.a_lock_refund_spend_tx = ci_from.createSCLockRefundSpendTx(
xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script,
pkh_refund_to,
xmr_offer.a_fee_rate, xmr_swap.vkbv
@@ -2326,7 +2302,7 @@ class BasicSwap(BaseApp):
# Double check txns before sending
self.log.debug('Bid: {} - Double checking chain A lock txns are valid before sending bid accept.'.format(bid_id.hex()))
check_lock_tx_inputs = False # TODO: check_lock_tx_inputs without txindex
_, xmr_swap.a_lock_tx_vout = ci_from.verifyLockTx(
_, xmr_swap.a_lock_tx_vout = ci_from.verifySCLockTx(
xmr_swap.a_lock_tx,
xmr_swap.a_lock_tx_script,
bid.amount,
@@ -2336,7 +2312,7 @@ class BasicSwap(BaseApp):
check_lock_tx_inputs,
xmr_swap.vkbv)
_, _, lock_refund_vout = ci_from.verifyLockRefundTx(
_, _, lock_refund_vout = ci_from.verifySCLockRefundTx(
xmr_swap.a_lock_refund_tx,
xmr_swap.a_lock_tx,
xmr_swap.a_lock_refund_tx_script,
@@ -2351,7 +2327,7 @@ class BasicSwap(BaseApp):
xmr_offer.a_fee_rate,
xmr_swap.vkbv)
ci_from.verifyLockRefundSpendTx(
ci_from.verifySCLockRefundSpendTx(
xmr_swap.a_lock_refund_spend_tx, xmr_swap.a_lock_refund_tx,
xmr_swap.a_lock_refund_tx_id, xmr_swap.a_lock_refund_tx_script,
xmr_swap.pkal,
@@ -2602,8 +2578,8 @@ class BasicSwap(BaseApp):
assert (addr_redeem_out is not None)
if self.coin_clients[coin_type]['use_segwit']:
# Change to btc hrp
addr_redeem_out = self.encodeSegwit(Coins.PART, self.decodeSegwit(coin_type, addr_redeem_out))
# Change to part hrp
addr_redeem_out = self.ci(Coins.PART).encodeSegwitAddress(ci.decodeSegwitAddress(addr_redeem_out))
else:
addr_redeem_out = replaceAddrPrefix(addr_redeem_out, Coins.PART, self.chain)
self.log.debug('addr_redeem_out %s', addr_redeem_out)
@@ -2704,8 +2680,8 @@ class BasicSwap(BaseApp):
addr_refund_out = self.getReceiveAddressFromPool(coin_type, bid.bid_id, tx_type)
ensure(addr_refund_out is not None, 'addr_refund_out is null')
if self.coin_clients[coin_type]['use_segwit']:
# Change to btc hrp
addr_refund_out = self.encodeSegwit(Coins.PART, self.decodeSegwit(coin_type, addr_refund_out))
# Change to part hrp
addr_refund_out = self.ci(Coins.PART).encodeSegwitAddress(ci.decodeSegwitAddress(addr_refund_out))
else:
addr_refund_out = replaceAddrPrefix(addr_refund_out, Coins.PART, self.chain)
self.log.debug('addr_refund_out %s', addr_refund_out)
@@ -2783,6 +2759,7 @@ class BasicSwap(BaseApp):
txid = self.ci(coin_to).publishTx(bytes.fromhex(txn))
self.log.debug('Submitted participate txn %s to %s chain for bid %s', txid, chainparams[coin_to]['name'], bid_id.hex())
bid.setPTxState(TxStates.TX_SENT)
self.logEvent(Concepts.BID, bid.bid_id, EventLogTypes.PTX_PUBLISHED, '', None)
else:
bid.participate_tx = SwapTx(
bid_id=bid_id,
@@ -2836,6 +2813,7 @@ class BasicSwap(BaseApp):
txn = self.createRedeemTxn(ci_to.coin_type(), bid)
txid = ci_to.publishTx(bytes.fromhex(txn))
self.log.debug('Submitted participate redeem txn %s to %s chain for bid %s', txid, ci_to.coin_name(), bid_id.hex())
self.logEvent(Concepts.BID, bid.bid_id, EventLogTypes.PTX_REDEEM_PUBLISHED, '', None)
# TX_REDEEMED will be set when spend is detected
# TODO: Wait for depth?
@@ -2861,7 +2839,6 @@ class BasicSwap(BaseApp):
# TODO: random offset into explorers, try blocks
for exp in explorers:
# TODO: ExplorerBitAps use only gettransaction if assert_txid is set
rv = exp.lookupUnspentByAddress(address)
@@ -3033,22 +3010,18 @@ class BasicSwap(BaseApp):
# TODO: Timeout waiting for transactions
bid_changed = False
a_lock_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_tx_script)
# Changed from ci_from.getOutput(bid.xmr_a_lock_tx.txid, a_lock_tx_dest, bid.amount, xmr_swap)
p2wsh_addr = ci_from.encode_p2wsh(a_lock_tx_dest)
lock_tx_chain_info = ci_from.getLockTxHeight(bid.xmr_a_lock_tx.txid, p2wsh_addr, bid.amount, bid.chain_a_height_start)
if offer.coin_from == Coins.FIRO:
lock_tx_chain_info = ci_from.getLockTxHeightFiro(bid.xmr_a_lock_tx.txid, xmr_swap.a_lock_tx_script, bid.amount, bid.chain_a_height_start)
else:
a_lock_tx_addr = ci_from.getSCLockScriptAddress(xmr_swap.a_lock_tx_script)
lock_tx_chain_info = ci_from.getLockTxHeight(bid.xmr_a_lock_tx.txid, a_lock_tx_addr, bid.amount, bid.chain_a_height_start)
if lock_tx_chain_info is None:
return rv
if not bid.xmr_a_lock_tx.chain_height and lock_tx_chain_info['height'] != 0:
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_SEEN, '', session)
block_header = ci_from.getBlockHeaderFromHeight(lock_tx_chain_info['height'])
bid.xmr_a_lock_tx.block_hash = bytes.fromhex(block_header['hash'])
bid.xmr_a_lock_tx.block_height = block_header['height']
bid.xmr_a_lock_tx.block_time = block_header['time'] # Or median_time?
self.setTxBlockInfoFromHeight(ci_from, bid.xmr_a_lock_tx, lock_tx_chain_info['height'])
bid_changed = True
if bid.xmr_a_lock_tx.chain_height != lock_tx_chain_info['height'] and lock_tx_chain_info['height'] != 0:
@@ -3149,15 +3122,14 @@ class BasicSwap(BaseApp):
if TxTypes.XMR_SWAP_A_LOCK_REFUND in bid.txns:
refund_tx = bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND]
if refund_tx.block_time is None:
a_lock_refund_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_refund_tx_script)
p2wsh_addr = ci_from.encode_p2wsh(a_lock_refund_tx_dest)
lock_refund_tx_chain_info = ci_from.getLockTxHeight(refund_tx.txid, p2wsh_addr, 0, bid.chain_a_height_start)
if offer.coin_from == Coins.FIRO:
lock_refund_tx_chain_info = ci_from.getLockTxHeightFiro(refund_tx.txid, xmr_swap.a_lock_refund_tx_script, 0, bid.chain_a_height_start)
else:
refund_tx_addr = ci_from.getSCLockScriptAddress(xmr_swap.a_lock_refund_tx_script)
lock_refund_tx_chain_info = ci_from.getLockTxHeight(refund_tx.txid, refund_tx_addr, 0, bid.chain_a_height_start)
if lock_refund_tx_chain_info is not None and lock_refund_tx_chain_info.get('height', 0) > 0:
block_header = ci_from.getBlockHeaderFromHeight(lock_refund_tx_chain_info['height'])
refund_tx.block_hash = bytes.fromhex(block_header['hash'])
refund_tx.block_height = block_header['height']
refund_tx.block_time = block_header['time'] # Or median_time?
self.setTxBlockInfoFromHeight(ci_from, refund_tx, lock_refund_tx_chain_info['height'])
self.saveBidInSession(bid_id, bid, session, xmr_swap)
session.commit()
@@ -3200,6 +3172,7 @@ class BasicSwap(BaseApp):
index = None
tx_height = None
last_initiate_txn_conf = bid.initiate_tx.conf
ci_from = self.ci(coin_from)
if coin_from == Coins.PART: # Has txindex
try:
initiate_txn = self.callcoinrpc(coin_from, 'getrawtransaction', [initiate_txnid_hex, True])
@@ -3223,7 +3196,6 @@ class BasicSwap(BaseApp):
else:
addr = p2sh
ci_from = self.ci(coin_from)
found = ci_from.getLockTxHeight(bytes.fromhex(initiate_txnid_hex), addr, bid.amount, bid.chain_a_height_start, find_index=True)
if found:
bid.initiate_tx.conf = found['depth']
@@ -3240,6 +3212,8 @@ class BasicSwap(BaseApp):
bid.initiate_tx.vout = index
# Start checking for spends of initiate_txn before fully confirmed
bid.initiate_tx.chain_height = self.setLastHeightChecked(coin_from, tx_height)
self.setTxBlockInfoFromHeight(ci_from, bid.initiate_tx, tx_height)
self.addWatchedOutput(coin_from, bid_id, initiate_txnid_hex, bid.initiate_tx.vout, BidStates.SWAP_INITIATED)
if bid.getITxState() is None or bid.getITxState() < TxStates.TX_SENT:
bid.setITxState(TxStates.TX_SENT)
@@ -3276,6 +3250,8 @@ class BasicSwap(BaseApp):
self.addParticipateTxn(bid_id, bid, coin_to, found['txid'], found['index'], found['height'])
bid.setPTxState(TxStates.TX_SENT)
save_bid = True
if found['height'] > 0 and bid.participate_tx.block_height is None:
self.setTxBlockInfoFromHeight(ci_to, bid.participate_tx, found['height'])
if bid.participate_tx.conf is not None:
self.log.debug('participate txid %s confirms %d', bid.participate_tx.txid.hex(), bid.participate_tx.conf)
@@ -3318,6 +3294,7 @@ class BasicSwap(BaseApp):
try:
txid = ci_from.publishTx(bid.initiate_txn_refund)
self.log.debug('Submitted initiate refund txn %s to %s chain for bid %s', txid, chainparams[coin_from]['name'], bid_id.hex())
self.logEvent(Concepts.BID, bid.bid_id, EventLogTypes.ITX_REFUND_PUBLISHED, '', None)
# State will update when spend is detected
except Exception as ex:
if 'non-BIP68-final' not in str(ex) and 'non-final' not in str(ex):
@@ -3328,6 +3305,7 @@ class BasicSwap(BaseApp):
try:
txid = ci_to.publishTx(bid.participate_txn_refund)
self.log.debug('Submitted participate refund txn %s to %s chain for bid %s', txid, chainparams[coin_to]['name'], bid_id.hex())
self.logEvent(Concepts.BID, bid.bid_id, EventLogTypes.PTX_REFUND_PUBLISHED, '', None)
# State will update when spend is detected
except Exception as ex:
if 'non-BIP68-final' not in str(ex) and 'non-final' not in str(ex):
@@ -4015,18 +3993,7 @@ class BasicSwap(BaseApp):
if swap_type == SwapTypes.SELLER_FIRST:
ensure(len(bid_data.pkhash_buyer) == 20, 'Bad pkhash_buyer length')
# Verify proof of funds
bid_proof_address = replaceAddrPrefix(bid_data.proof_address, Coins.PART, self.chain)
mm = chainparams[coin_to]['message_magic']
passed = self.ci(Coins.PART).verifyMessage(bid_proof_address, bid_data.proof_address + '_swap_proof_' + offer_id.hex(), bid_data.proof_signature, mm)
ensure(passed is True, 'Proof of funds signature invalid')
if self.coin_clients[coin_to]['use_segwit']:
addr_search = self.encodeSegwit(coin_to, decodeAddress(bid_data.proof_address)[1:])
else:
addr_search = bid_data.proof_address
sum_unspent = self.getAddressBalance(coin_to, addr_search)
sum_unspent = ci_to.verifyProofOfFunds(bid_data.proof_address, bid_data.proof_signature, offer_id)
self.log.debug('Proof of funds %s %s', bid_data.proof_address, self.ci(coin_to).format_amount(sum_unspent))
ensure(sum_unspent >= amount_to, 'Proof of funds failed')
@@ -4382,7 +4349,7 @@ class BasicSwap(BaseApp):
# TODO: check_lock_tx_inputs without txindex
check_a_lock_tx_inputs = False
xmr_swap.a_lock_tx_id, xmr_swap.a_lock_tx_vout = ci_from.verifyLockTx(
xmr_swap.a_lock_tx_id, xmr_swap.a_lock_tx_vout = ci_from.verifySCLockTx(
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script,
bid.amount,
xmr_swap.pkal, xmr_swap.pkaf,
@@ -4390,14 +4357,14 @@ class BasicSwap(BaseApp):
check_a_lock_tx_inputs, xmr_swap.vkbv)
a_lock_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_tx_script)
xmr_swap.a_lock_refund_tx_id, xmr_swap.a_swap_refund_value, lock_refund_vout = ci_from.verifyLockRefundTx(
xmr_swap.a_lock_refund_tx_id, xmr_swap.a_swap_refund_value, lock_refund_vout = ci_from.verifySCLockRefundTx(
xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_tx, xmr_swap.a_lock_refund_tx_script,
xmr_swap.a_lock_tx_id, xmr_swap.a_lock_tx_vout, xmr_offer.lock_time_1, xmr_swap.a_lock_tx_script,
xmr_swap.pkal, xmr_swap.pkaf,
xmr_offer.lock_time_2,
bid.amount, xmr_offer.a_fee_rate, xmr_swap.vkbv)
ci_from.verifyLockRefundSpendTx(
ci_from.verifySCLockRefundSpendTx(
xmr_swap.a_lock_refund_spend_tx, xmr_swap.a_lock_refund_tx,
xmr_swap.a_lock_refund_tx_id, xmr_swap.a_lock_refund_tx_script,
xmr_swap.pkal,
@@ -4517,7 +4484,7 @@ class BasicSwap(BaseApp):
xmr_swap.kal_sig = ci_from.signCompact(kal, 'proof key owned for swap')
# Create Script lock spend tx
xmr_swap.a_lock_spend_tx = ci_from.createScriptLockSpendTx(
xmr_swap.a_lock_spend_tx = ci_from.createSCLockSpendTx(
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script,
xmr_swap.dest_af,
xmr_offer.a_fee_rate, xmr_swap.vkbv)
@@ -4923,12 +4890,12 @@ class BasicSwap(BaseApp):
xmr_swap.a_lock_spend_tx_id = ci_from.getTxid(xmr_swap.a_lock_spend_tx)
xmr_swap.kal_sig = msg_data.kal_sig
ci_from.verifyLockSpendTx(
ci_from.verifySCLockSpendTx(
xmr_swap.a_lock_spend_tx,
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script,
xmr_swap.dest_af, xmr_offer.a_fee_rate, xmr_swap.vkbv)
ci_from.verifyCompact(xmr_swap.pkal, 'proof key owned for swap', xmr_swap.kal_sig)
ci_from.verifyCompactSig(xmr_swap.pkal, 'proof key owned for swap', xmr_swap.kal_sig)
bid.setState(BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX)
bid.setState(BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX)
@@ -5865,7 +5832,7 @@ class BasicSwap(BaseApp):
self.log.debug('Creating %s lock refund swipe tx', ci.coin_name())
pkh_dest = ci.decodeAddress(self.getReceiveAddressForCoin(ci.coin_type()))
spend_tx = ci.createScriptLockRefundSpendToFTx(
spend_tx = ci.createSCLockRefundSpendToFTx(
xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script,
pkh_dest,
xmr_offer.a_fee_rate, xmr_swap.vkbv)

View File

@@ -9,8 +9,8 @@ import struct
import hashlib
from enum import IntEnum, auto
from .util.address import (
decodeAddress,
encodeAddress,
decodeAddress,
)
from .chainparams import (
chainparams,
@@ -162,6 +162,12 @@ class EventLogTypes(IntEnum):
ERROR = auto()
AUTOMATION_CONSTRAINT = auto()
AUTOMATION_ACCEPTING_BID = auto()
ITX_PUBLISHED = auto()
ITX_REDEEM_PUBLISHED = auto()
ITX_REFUND_PUBLISHED = auto()
PTX_PUBLISHED = auto()
PTX_REDEEM_PUBLISHED = auto()
PTX_REFUND_PUBLISHED = auto()
class XmrSplitMsgTypes(IntEnum):
@@ -357,6 +363,18 @@ def describeEventEntry(event_type, event_msg):
return 'Failed auto accepting'
if event_type == EventLogTypes.AUTOMATION_ACCEPTING_BID:
return 'Auto accepting'
if event_type == EventLogTypes.ITX_PUBLISHED:
return 'Initiate tx published'
if event_type == EventLogTypes.ITX_REDEEM_PUBLISHED:
return 'Initiate tx redeem tx published'
if event_type == EventLogTypes.ITX_REFUND_PUBLISHED:
return 'Initiate tx refund tx published'
if event_type == EventLogTypes.PTX_PUBLISHED:
return 'Participate tx published'
if event_type == EventLogTypes.PTX_REDEEM_PUBLISHED:
return 'Participate tx redeem tx published'
if event_type == EventLogTypes.PTX_REFUND_PUBLISHED:
return 'Participate tx refund tx published'
def getVoutByAddress(txjs, p2sh):

View File

@@ -30,6 +30,7 @@ class Coins(IntEnum):
# NDAU = 10
PIVX = 11
DASH = 12
FIRO = 13
chainparams = {
@@ -287,6 +288,45 @@ chainparams = {
'min_amount': 1000,
'max_amount': 100000 * COIN,
}
},
Coins.FIRO: {
'name': 'firo',
'ticker': 'FIRO',
'message_magic': 'Zcoin Signed Message:\n',
'blocks_target': 60 * 10,
'decimal_places': 8,
'has_csv': True,
'has_segwit': True,
'mainnet': {
'rpcport': 8888,
'pubkey_address': 82,
'script_address': 7,
'key_prefix': 210,
'hrp': '',
'bip44': 136,
'min_amount': 1000,
'max_amount': 100000 * COIN,
},
'testnet': {
'rpcport': 18888,
'pubkey_address': 65,
'script_address': 178,
'key_prefix': 185,
'hrp': '',
'bip44': 1,
'min_amount': 1000,
'max_amount': 100000 * COIN,
},
'regtest': {
'rpcport': 28888,
'pubkey_address': 65,
'script_address': 178,
'key_prefix': 185,
'hrp': '',
'bip44': 1,
'min_amount': 1000,
'max_amount': 100000 * COIN,
}
}
}
ticker_map = {}

View File

@@ -46,3 +46,8 @@ DASH_BINDIR = os.path.expanduser(os.getenv('DASH_BINDIR', os.path.join(DEFAULT_T
DASHD = os.getenv('DASHD', 'dashd' + bin_suffix)
DASH_CLI = os.getenv('DASH_CLI', 'dash-cli' + bin_suffix)
DASH_TX = os.getenv('DASH_TX', 'dash-tx' + bin_suffix)
FIRO_BINDIR = os.path.expanduser(os.getenv('FIRO_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'firo')))
FIROD = os.getenv('FIROD', 'firod' + bin_suffix)
FIRO_CLI = os.getenv('FIRO_CLI', 'firo-cli' + bin_suffix)
FIRO_TX = os.getenv('FIRO_TX', 'firo-tx' + bin_suffix)

View File

@@ -320,7 +320,7 @@ class BTCInterface(CoinInterface):
def decodeAddress(self, address):
bech32_prefix = self.chainparams_network()['hrp']
if address.startswith(bech32_prefix + '1'):
if len(bech32_prefix) > 0 and address.startswith(bech32_prefix + '1'):
return bytes(segwit_addr.decode(bech32_prefix, address)[1])
return decodeAddress(address)[1:]
@@ -338,12 +338,22 @@ class BTCInterface(CoinInterface):
checksum = hashlib.sha256(hashlib.sha256(data).digest()).digest()
return b58encode(data + checksum[0:4])
def sh_to_address(self, sh):
assert (len(sh) == 20)
prefix = self.chainparams_network()['script_address']
data = bytes((prefix,)) + sh
checksum = hashlib.sha256(hashlib.sha256(data).digest()).digest()
return b58encode(data + checksum[0:4])
def encode_p2wsh(self, script):
bech32_prefix = self.chainparams_network()['hrp']
version = 0
program = script[2:] # strip version and length
return segwit_addr.encode(bech32_prefix, version, program)
def encodeScriptDest(self, script):
return self.encode_p2wsh(script)
def encode_p2sh(self, script):
return pubkeyToAddress(self.chainparams_network()['script_address'], script)
@@ -361,6 +371,9 @@ class BTCInterface(CoinInterface):
pk = self.getPubkey(key)
return hash160(pk)
def getSeedHash(self, seed):
return self.getAddressHashFromKey(seed)[::-1]
def verifyKey(self, k):
i = b2i(k)
return (i < ep.o and i > 0)
@@ -375,6 +388,12 @@ class BTCInterface(CoinInterface):
def encodePubkey(self, pk):
return pointToCPK(pk)
def encodeSegwitAddress(self, key_hash):
return segwit_addr.encode(self.chainparams_network()['hrp'], 0, key_hash)
def decodeSegwitAddress(self, addr):
return bytes(segwit_addr.decode(self.chainparams_network()['hrp'], addr)[1])
def decodePubkey(self, pke):
return CPKToPoint(pke)
@@ -415,7 +434,7 @@ class BTCInterface(CoinInterface):
return CScript([2, Kal_enc, Kaf_enc, 2, CScriptOp(OP_CHECKMULTISIG)])
def createScriptLockTx(self, value, Kal, Kaf, vkbv=None):
def createSCLockTx(self, value, Kal, Kaf, vkbv=None):
script = self.genScriptLockTxScript(Kal, Kaf)
tx = CTransaction()
tx.nVersion = self.txVersion()
@@ -423,7 +442,7 @@ class BTCInterface(CoinInterface):
return tx.serialize(), script
def fundScriptLockTx(self, tx_bytes, feerate, vkbv=None):
def fundSCLockTx(self, tx_bytes, feerate, vkbv=None):
return self.fundTx(tx_bytes, feerate)
def extractScriptLockRefundScriptValues(self, script_bytes):
@@ -470,11 +489,11 @@ class BTCInterface(CoinInterface):
Kaf_enc, CScriptOp(OP_CHECKSIG),
CScriptOp(OP_ENDIF)])
def createScriptLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv=None):
def createSCLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv=None):
tx_lock = CTransaction()
tx_lock = FromHex(tx_lock, tx_lock_bytes.hex())
output_script = CScript([OP_0, hashlib.sha256(script_lock).digest()])
output_script = self.getScriptDest(script_lock)
locked_n = findOutput(tx_lock, output_script)
ensure(locked_n is not None, 'Output not found in tx')
locked_coin = tx_lock.vout[locked_n].nValue
@@ -485,8 +504,10 @@ class BTCInterface(CoinInterface):
refund_script = self.genScriptLockRefundTxScript(Kal, Kaf, csv_val)
tx = CTransaction()
tx.nVersion = self.txVersion()
tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n), nSequence=lock1_value))
tx.vout.append(self.txoType()(locked_coin, CScript([OP_0, hashlib.sha256(refund_script).digest()])))
tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n),
nSequence=lock1_value,
scriptSig=self.getScriptScriptSig(script_lock)))
tx.vout.append(self.txoType()(locked_coin, self.getScriptDest(refund_script)))
dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock)
witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack)
@@ -495,19 +516,19 @@ class BTCInterface(CoinInterface):
tx.vout[0].nValue = locked_coin - pay_fee
tx.rehash()
self._log.info('createScriptLockRefundTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
self._log.info('createSCLockRefundTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
return tx.serialize(), refund_script, tx.vout[0].nValue
def createScriptLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate, vkbv=None):
def createSCLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate, vkbv=None):
# Returns the coinA locked coin to the leader
# The follower will sign the multisig path with a signature encumbered by the leader's coinB spend pubkey
# If the leader publishes the decrypted signature the leader's coinB spend privatekey will be revealed to the follower
tx_lock_refund = self.loadTx(tx_lock_refund_bytes)
output_script = CScript([OP_0, hashlib.sha256(script_lock_refund).digest()])
output_script = self.getScriptDest(script_lock_refund)
locked_n = findOutput(tx_lock_refund, output_script)
ensure(locked_n is not None, 'Output not found in tx')
locked_coin = tx_lock_refund.vout[locked_n].nValue
@@ -517,7 +538,9 @@ class BTCInterface(CoinInterface):
tx = CTransaction()
tx.nVersion = self.txVersion()
tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n), nSequence=0))
tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n),
nSequence=0,
scriptSig=self.getScriptScriptSig(script_lock_refund)))
tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_refund_to)))
@@ -528,18 +551,18 @@ class BTCInterface(CoinInterface):
tx.vout[0].nValue = locked_coin - pay_fee
tx.rehash()
self._log.info('createScriptLockRefundSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
self._log.info('createSCLockRefundSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
return tx.serialize()
def createScriptLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv=None):
def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv=None):
# lock refund swipe tx
# Sends the coinA locked coin to the follower
tx_lock_refund = self.loadTx(tx_lock_refund_bytes)
output_script = CScript([OP_0, hashlib.sha256(script_lock_refund).digest()])
output_script = self.getScriptDest(script_lock_refund)
locked_n = findOutput(tx_lock_refund, output_script)
ensure(locked_n is not None, 'Output not found in tx')
locked_coin = tx_lock_refund.vout[locked_n].nValue
@@ -551,7 +574,9 @@ class BTCInterface(CoinInterface):
tx = CTransaction()
tx.nVersion = self.txVersion()
tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n), nSequence=lock2_value))
tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n),
nSequence=lock2_value,
scriptSig=self.getScriptScriptSig(script_lock_refund)))
tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest)))
@@ -562,14 +587,14 @@ class BTCInterface(CoinInterface):
tx.vout[0].nValue = locked_coin - pay_fee
tx.rehash()
self._log.info('createScriptLockRefundSpendToFTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
self._log.info('createSCLockRefundSpendToFTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
return tx.serialize()
def createScriptLockSpendTx(self, tx_lock_bytes, script_lock, pkh_dest, tx_fee_rate, vkbv=None):
def createSCLockSpendTx(self, tx_lock_bytes, script_lock, pkh_dest, tx_fee_rate, vkbv=None):
tx_lock = self.loadTx(tx_lock_bytes)
output_script = CScript([OP_0, hashlib.sha256(script_lock).digest()])
output_script = self.getScriptDest(script_lock)
locked_n = findOutput(tx_lock, output_script)
ensure(locked_n is not None, 'Output not found in tx')
locked_coin = tx_lock.vout[locked_n].nValue
@@ -579,7 +604,8 @@ class BTCInterface(CoinInterface):
tx = CTransaction()
tx.nVersion = self.txVersion()
tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n)))
tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n),
scriptSig=self.getScriptScriptSig(script_lock)))
tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest)))
@@ -590,16 +616,16 @@ class BTCInterface(CoinInterface):
tx.vout[0].nValue = locked_coin - pay_fee
tx.rehash()
self._log.info('createScriptLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
self._log.info('createSCLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
return tx.serialize()
def verifyLockTx(self, tx_bytes, script_out,
swap_value,
Kal, Kaf,
feerate,
check_lock_tx_inputs, vkbv=None):
def verifySCLockTx(self, tx_bytes, script_out,
swap_value,
Kal, Kaf,
feerate,
check_lock_tx_inputs, vkbv=None):
# Verify:
#
@@ -614,7 +640,7 @@ class BTCInterface(CoinInterface):
ensure(tx.nVersion == self.txVersion(), 'Bad version')
ensure(tx.nLockTime == 0, 'Bad nLockTime') # TODO match txns created by cores
script_pk = CScript([OP_0, hashlib.sha256(script_out).digest()])
script_pk = self.getScriptDest(script_out)
locked_n = findOutput(tx, script_pk)
ensure(locked_n is not None, 'Output not found in tx')
locked_coin = tx.vout[locked_n].nValue
@@ -663,9 +689,9 @@ class BTCInterface(CoinInterface):
return txid, locked_n
def verifyLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out,
prevout_id, prevout_n, prevout_seq, prevout_script,
Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv=None):
def verifySCLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out,
prevout_id, prevout_n, prevout_seq, prevout_script,
Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv=None):
# Verify:
# Must have only one input with correct prevout and sequence
# Must have only one output to the p2wsh of the lock refund script
@@ -680,12 +706,12 @@ class BTCInterface(CoinInterface):
ensure(len(tx.vin) == 1, 'tx doesn\'t have one input')
ensure(tx.vin[0].nSequence == prevout_seq, 'Bad input nSequence')
ensure(len(tx.vin[0].scriptSig) == 0, 'Input scriptsig not empty')
ensure(tx.vin[0].scriptSig == self.getScriptScriptSig(prevout_script), 'Input scriptsig mismatch')
ensure(tx.vin[0].prevout.hash == b2i(prevout_id) and tx.vin[0].prevout.n == prevout_n, 'Input prevout mismatch')
ensure(len(tx.vout) == 1, 'tx doesn\'t have one output')
script_pk = CScript([OP_0, hashlib.sha256(script_out).digest()])
script_pk = self.getScriptDest(script_out)
locked_n = findOutput(tx, script_pk)
ensure(locked_n is not None, 'Output not found in tx')
locked_coin = tx.vout[locked_n].nValue
@@ -712,10 +738,10 @@ class BTCInterface(CoinInterface):
return txid, locked_coin, locked_n
def verifyLockRefundSpendTx(self, tx_bytes, lock_refund_tx_bytes,
lock_refund_tx_id, prevout_script,
Kal,
prevout_n, prevout_value, feerate, vkbv=None):
def verifySCLockRefundSpendTx(self, tx_bytes, lock_refund_tx_bytes,
lock_refund_tx_id, prevout_script,
Kal,
prevout_n, prevout_value, feerate, vkbv=None):
# Verify:
# Must have only one input with correct prevout (n is always 0) and sequence
# Must have only one output sending lock refund tx value - fee to leader's address, TODO: follower shouldn't need to verify destination addr
@@ -728,7 +754,7 @@ class BTCInterface(CoinInterface):
ensure(len(tx.vin) == 1, 'tx doesn\'t have one input')
ensure(tx.vin[0].nSequence == 0, 'Bad input nSequence')
ensure(len(tx.vin[0].scriptSig) == 0, 'Input scriptsig not empty')
ensure(tx.vin[0].scriptSig == self.getScriptScriptSig(prevout_script), 'Input scriptsig mismatch')
ensure(tx.vin[0].prevout.hash == b2i(lock_refund_tx_id) and tx.vin[0].prevout.n == 0, 'Input prevout mismatch')
ensure(len(tx.vout) == 1, 'tx doesn\'t have one output')
@@ -756,9 +782,9 @@ class BTCInterface(CoinInterface):
return True
def verifyLockSpendTx(self, tx_bytes,
lock_tx_bytes, lock_tx_script,
a_pkhash_f, feerate, vkbv=None):
def verifySCLockSpendTx(self, tx_bytes,
lock_tx_bytes, lock_tx_script,
a_pkhash_f, feerate, vkbv=None):
# Verify:
# Must have only one input with correct prevout (n is always 0) and sequence
# Must have only one output with destination and amount
@@ -774,13 +800,13 @@ class BTCInterface(CoinInterface):
lock_tx = self.loadTx(lock_tx_bytes)
lock_tx_id = self.getTxid(lock_tx)
output_script = CScript([OP_0, hashlib.sha256(lock_tx_script).digest()])
output_script = self.getScriptDest(lock_tx_script)
locked_n = findOutput(lock_tx, output_script)
ensure(locked_n is not None, 'Output not found in tx')
locked_coin = lock_tx.vout[locked_n].nValue
ensure(tx.vin[0].nSequence == 0, 'Bad input nSequence')
ensure(len(tx.vin[0].scriptSig) == 0, 'Input scriptsig not empty')
ensure(tx.vin[0].scriptSig == self.getScriptScriptSig(lock_tx_script), 'Input scriptsig mismatch')
ensure(tx.vin[0].prevout.hash == b2i(lock_tx_id) and tx.vin[0].prevout.n == locked_n, 'Input prevout mismatch')
ensure(len(tx.vout) == 1, 'tx doesn\'t have one output')
@@ -891,7 +917,7 @@ class BTCInterface(CoinInterface):
def getTxOutputPos(self, tx, script):
if isinstance(tx, bytes):
tx = self.loadTx(tx)
script_pk = CScript([OP_0, hashlib.sha256(script).digest()])
script_pk = self.getScriptDest(script)
return findOutput(tx, script_pk)
def getPubkeyHash(self, K):
@@ -900,6 +926,9 @@ class BTCInterface(CoinInterface):
def getScriptDest(self, script):
return CScript([OP_0, hashlib.sha256(script).digest()])
def getScriptScriptSig(self, script):
return bytes()
def getPkDest(self, K):
return self.getScriptForPubkeyHash(self.getPubkeyHash(K))
@@ -1003,11 +1032,22 @@ class BTCInterface(CoinInterface):
def spendBLockTx(self, chain_b_lock_txid, address_to, kbv, kbs, cb_swap_value, b_fee, restore_height):
raise ValueError('TODO')
def importWatchOnlyAddress(self, address, label):
self.rpc_callback('importaddress', [address, label, False])
def isWatchOnlyAddress(self, address):
addr_info = self.rpc_callback('getaddressinfo', [address])
return addr_info['iswatchonly']
def getSCLockScriptAddress(self, lock_script):
lock_tx_dest = self.getScriptDest(lock_script)
return self.encodeScriptDest(lock_tx_dest)
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index=False):
# Add watchonly address and rescan if required
addr_info = self.rpc_callback('getaddressinfo', [dest_address])
if not addr_info['iswatchonly']:
ro = self.rpc_callback('importaddress', [dest_address, 'bid', False])
if not self.isWatchOnlyAddress(dest_address):
self.importWatchOnlyAddress(dest_address, 'bid')
self._log.info('Imported watch-only addr: {}'.format(dest_address))
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), rescan_from))
self.rpc_callback('rescanblockchain', [rescan_from])
@@ -1082,7 +1122,7 @@ class BTCInterface(CoinInterface):
privkey = PrivateKey(k)
return privkey.sign_recoverable(message_hash, hasher=None)[:64]
def verifyCompact(self, K, message, sig):
def verifyCompactSig(self, K, message, sig):
message_hash = hashlib.sha256(bytes(message, 'utf-8')).digest()
pubkey = PublicKey(K)
rv = pubkey.verify_compact(sig, message_hash, hasher=None)
@@ -1090,7 +1130,7 @@ class BTCInterface(CoinInterface):
def verifyMessage(self, address, message, signature, message_magic=None) -> bool:
if message_magic is None:
message_magic = self.chainparams_network()['message_magic']
message_magic = self.chainparams()['message_magic']
message_bytes = SerialiseNumCompact(len(message_magic)) + bytes(message_magic, 'utf-8') + SerialiseNumCompact(len(message)) + bytes(message, 'utf-8')
message_hash = hashlib.sha256(hashlib.sha256(message_bytes).digest()).digest()
@@ -1185,9 +1225,61 @@ class BTCInterface(CoinInterface):
def getBlockWithTxns(self, block_hash):
return self.rpc_callback('getblock', [block_hash, 2])
def getUnspentsByAddr(self):
unspent_addr = dict()
unspent = self.rpc_callback('listunspent')
for u in unspent:
if u['spendable'] is not True:
continue
unspent_addr[u['address']] = unspent_addr.get(u['address'], 0) + self.make_int(u['amount'], r=1)
return unspent_addr
def getUTXOBalance(self, address):
num_blocks = self.rpc_callback('getblockcount')
sum_unspent = 0
self._log.debug('[rm] scantxoutset start') # scantxoutset is slow
ro = self.rpc_callback('scantxoutset', ['start', ['addr({})'.format(address)]]) # TODO: Use combo(address) where possible
self._log.debug('[rm] scantxoutset end')
for o in ro['unspents']:
sum_unspent += self.make_int(o['amount'])
return sum_unspent
def getProofOfFunds(self, amount_for, extra_commit_bytes):
# TODO: Lock unspent and use same output/s to fund bid
unspent_addr = self.getUnspentsByAddr()
sign_for_addr = None
for addr, value in unspent_addr.items():
if value >= amount_for:
sign_for_addr = addr
break
ensure(sign_for_addr is not None, 'Could not find address with enough funds for proof')
self._log.debug('sign_for_addr %s', sign_for_addr)
if self._use_segwit: # TODO: Use isSegwitAddress when scantxoutset can use combo
# 'Address does not refer to key' for non p2pkh
pkh = self.decodeAddress(sign_for_addr)
sign_for_addr = self.pkh_to_address(pkh)
self._log.debug('sign_for_addr converted %s', sign_for_addr)
signature = self.rpc_callback('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + extra_commit_bytes.hex()])
return (sign_for_addr, signature)
def verifyProofOfFunds(self, address, signature, extra_commit_bytes):
passed = self.verifyMessage(address, address + '_swap_proof_' + extra_commit_bytes.hex(), signature)
ensure(passed is True, 'Proof of funds signature invalid')
if self._use_segwit:
address = self.encodeSegwitAddress(decodeAddress(address)[1:])
return self.getUTXOBalance(address)
def testBTCInterface():
print('testBTCInterface')
print('TODO: testBTCInterface')
if __name__ == "__main__":

175
basicswap/interface/firo.py Normal file
View File

@@ -0,0 +1,175 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2022 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import hashlib
from .btc import BTCInterface, find_vout_for_address_from_txobj
from basicswap.chainparams import Coins
from basicswap.util.address import (
decodeAddress,
)
from basicswap.contrib.test_framework.script import (
CScript,
OP_0,
OP_DUP,
OP_EQUAL,
OP_HASH160,
OP_CHECKSIG,
OP_EQUALVERIFY,
hash160,
)
from basicswap.contrib.test_framework.messages import (
CTransaction,
)
class FIROInterface(BTCInterface):
@staticmethod
def coin_type():
return Coins.FIRO
def initialiseWallet(self, key):
# load with -hdseed= parameter
pass
def getNewAddress(self, use_segwit, label='swap_receive'):
return self.rpc_callback('getnewaddress', [label])
# addr_plain = self.rpc_callback('getnewaddress', [label])
# return self.rpc_callback('addwitnessaddress', [addr_plain])
def decodeAddress(self, address):
return decodeAddress(address)[1:]
def encodeSegwitAddress(self, script):
raise ValueError('TODO')
def decodeSegwitAddress(self, addr):
raise ValueError('TODO')
def isWatchOnlyAddress(self, address):
addr_info = self.rpc_callback('validateaddress', [address])
return addr_info['iswatchonly']
def getSCLockScriptAddress(self, lock_script):
lock_tx_dest = self.getScriptDest(lock_script)
address = self.encodeScriptDest(lock_tx_dest)
if not self.isWatchOnlyAddress(address):
# Expects P2WSH nested in BIP16_P2SH
ro = self.rpc_callback('importaddress', [lock_tx_dest.hex(), 'bid lock', False, True])
addr_info = self.rpc_callback('validateaddress', [address])
return address
def getLockTxHeightFiro(self, txid, lock_script, bid_amount, rescan_from, find_index=False):
# Add watchonly address and rescan if required
lock_tx_dest = self.getScriptDest(lock_script)
dest_address = self.encodeScriptDest(lock_tx_dest)
if not self.isWatchOnlyAddress(dest_address):
self.rpc_callback('importaddress', [lock_tx_dest.hex(), 'bid lock', False, True])
self._log.info('Imported watch-only addr: {}'.format(dest_address))
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), rescan_from))
self.rpc_callback('rescanblockchain', [rescan_from])
return_txid = True if txid is None else False
if txid is None:
txns = self.rpc_callback('listunspent', [0, 9999999, [dest_address, ]])
for tx in txns:
if self.make_int(tx['amount']) == bid_amount:
txid = bytes.fromhex(tx['txid'])
break
if txid is None:
return None
try:
tx = self.rpc_callback('gettransaction', [txid.hex()])
block_height = 0
if 'blockhash' in tx:
block_header = self.rpc_callback('getblockheader', [tx['blockhash']])
block_height = block_header['height']
rv = {
'depth': 0 if 'confirmations' not in tx else tx['confirmations'],
'height': block_height}
except Exception as e:
self._log.debug('getLockTxHeight gettransaction failed: %s, %s', txid.hex(), str(e))
return None
if find_index:
tx_obj = self.rpc_callback('decoderawtransaction', [tx['hex']])
rv['index'] = find_vout_for_address_from_txobj(tx_obj, dest_address)
if return_txid:
rv['txid'] = txid.hex()
return rv
def createSCLockTx(self, value, Kal, Kaf, vkbv=None):
script = self.genScriptLockTxScript(Kal, Kaf)
tx = CTransaction()
tx.nVersion = self.txVersion()
tx.vout.append(self.txoType()(value, self.getScriptDest(script)))
return tx.serialize(), script
def fundSCLockTx(self, tx_bytes, feerate, vkbv=None):
return self.fundTx(tx_bytes, feerate)
def signTxWithWallet(self, tx):
rv = self.rpc_callback('signrawtransaction', [tx.hex()])
return bytes.fromhex(rv['hex'])
def createRawSignedTransaction(self, addr_to, amount):
txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}')
options = {
'lockUnspents': True,
'feeRate': fee_rate,
}
txn_funded = self.rpc_callback('fundrawtransaction', [txn, options])['hex']
txn_signed = self.rpc_callback('signrawtransaction', [txn_funded])['hex']
return txn_signed
def getScriptForPubkeyHash(self, pkh):
# Return P2WPKH nested in BIP16 P2SH
return CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG])
def getScriptDest(self, script):
# P2WSH nested in BIP16_P2SH
script_hash = hashlib.sha256(script).digest()
assert len(script_hash) == 32
script_hash_hash = hash160(script_hash)
assert len(script_hash_hash) == 20
return CScript([OP_HASH160, script_hash_hash, OP_EQUAL])
def getSeedHash(self, seed):
return hash160(seed)[::-1]
def encodeScriptDest(self, script):
# Extract hash from script
script_hash = script[2:-1]
return self.sh_to_address(script_hash)
def getScriptScriptSig(self, script):
return CScript([OP_0, hashlib.sha256(script).digest()])
def withdrawCoin(self, value, addr_to, subfee):
params = [addr_to, value, '', '', subfee]
return self.rpc_callback('sendtoaddress', params)
def getWalletSeedID(self):
return self.rpc_callback('getwalletinfo')['hdmasterkeyid']

View File

@@ -166,7 +166,7 @@ class PARTInterfaceBlind(PARTInterface):
ensure(v['result'] is True, 'verifycommitment failed')
return output_n, blinded_info
def createScriptLockTx(self, value, Kal, Kaf, vkbv):
def createSCLockTx(self, value, Kal, Kaf, vkbv):
script = self.genScriptLockTxScript(Kal, Kaf)
# Nonce is derived from vkbv, ephemeral_key isn't used
@@ -183,7 +183,7 @@ class PARTInterfaceBlind(PARTInterface):
tx_bytes = bytes.fromhex(rv['hex'])
return tx_bytes, script
def fundScriptLockTx(self, tx_bytes, feerate, vkbv):
def fundSCLockTx(self, tx_bytes, feerate, vkbv):
feerate_str = self.format_amount(feerate)
# TODO: unlock unspents if bid cancelled
@@ -205,7 +205,7 @@ class PARTInterfaceBlind(PARTInterface):
rv = self.rpc_callback('fundrawtransactionfrom', ['blind', tx_hex, {}, outputs_info, options])
return bytes.fromhex(rv['hex'])
def createScriptLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv):
def createSCLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv):
lock_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_bytes.hex()])
assert (self.getTxid(tx_lock_bytes).hex() == lock_tx_obj['txid'])
# Nonce is derived from vkbv, ephemeral_key isn't used
@@ -252,7 +252,7 @@ class PARTInterfaceBlind(PARTInterface):
return bytes.fromhex(lock_refund_tx_hex), refund_script, refunded_value
def createScriptLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate, vkbv):
def createSCLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate, vkbv):
# Returns the coinA locked coin to the leader
# The follower will sign the multisig path with a signature encumbered by the leader's coinB spend pubkey
# If the leader publishes the decrypted signature the leader's coinB spend privatekey will be revealed to the follower
@@ -297,11 +297,11 @@ class PARTInterfaceBlind(PARTInterface):
return bytes.fromhex(lock_refund_spend_tx_hex)
def verifyLockTx(self, tx_bytes, script_out,
swap_value,
Kal, Kaf,
feerate,
check_lock_tx_inputs, vkbv):
def verifySCLockTx(self, tx_bytes, script_out,
swap_value,
Kal, Kaf,
feerate,
check_lock_tx_inputs, vkbv):
lock_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()])
lock_txid_hex = lock_tx_obj['txid']
self._log.info('Verifying lock tx: {}.'.format(lock_txid_hex))
@@ -341,9 +341,9 @@ class PARTInterfaceBlind(PARTInterface):
return bytes.fromhex(lock_txid_hex), lock_output_n
def verifyLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out,
prevout_id, prevout_n, prevout_seq, prevout_script,
Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv):
def verifySCLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out,
prevout_id, prevout_n, prevout_seq, prevout_script,
Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv):
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()])
lock_refund_txid_hex = lock_refund_tx_obj['txid']
self._log.info('Verifying lock refund tx: {}.'.format(lock_refund_txid_hex))
@@ -399,10 +399,10 @@ class PARTInterfaceBlind(PARTInterface):
return bytes.fromhex(lock_refund_txid_hex), lock_refund_txo_value, lock_refund_output_n
def verifyLockRefundSpendTx(self, tx_bytes, lock_refund_tx_bytes,
lock_refund_tx_id, prevout_script,
Kal,
prevout_n, prevout_value, feerate, vkbv):
def verifySCLockRefundSpendTx(self, tx_bytes, lock_refund_tx_bytes,
lock_refund_tx_id, prevout_script,
Kal,
prevout_n, prevout_value, feerate, vkbv):
lock_refund_spend_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()])
lock_refund_spend_txid_hex = lock_refund_spend_tx_obj['txid']
self._log.info('Verifying lock refund spend tx: {}.'.format(lock_refund_spend_txid_hex))
@@ -460,7 +460,7 @@ class PARTInterfaceBlind(PARTInterface):
ensure(output_n is not None, 'Output not found in tx')
return output_n
def createScriptLockSpendTx(self, tx_lock_bytes, script_lock, pk_dest, tx_fee_rate, vkbv):
def createSCLockSpendTx(self, tx_lock_bytes, script_lock, pk_dest, tx_fee_rate, vkbv):
lock_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_bytes.hex()])
lock_txid_hex = lock_tx_obj['txid']
@@ -501,14 +501,14 @@ class PARTInterfaceBlind(PARTInterface):
vsize = lock_spend_tx_obj['vsize']
pay_fee = make_int(lock_spend_tx_obj['vout'][0]['ct_fee'])
actual_tx_fee_rate = pay_fee * 1000 // vsize
self._log.info('createScriptLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
self._log.info('createSCLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
lock_spend_tx_obj['txid'], actual_tx_fee_rate, vsize, pay_fee)
return bytes.fromhex(lock_spend_tx_hex)
def verifyLockSpendTx(self, tx_bytes,
lock_tx_bytes, lock_tx_script,
a_pk_f, feerate, vkbv):
def verifySCLockSpendTx(self, tx_bytes,
lock_tx_bytes, lock_tx_script,
a_pk_f, feerate, vkbv):
lock_spend_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()])
lock_spend_txid_hex = lock_spend_tx_obj['txid']
self._log.info('Verifying lock spend tx: {}.'.format(lock_spend_txid_hex))
@@ -578,7 +578,7 @@ class PARTInterfaceBlind(PARTInterface):
return True
def createScriptLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv):
def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv):
# lock refund swipe tx
# Sends the coinA locked coin to the follower
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_refund_bytes.hex()])

View File

@@ -241,6 +241,7 @@ def js_bids(self, url_split, post_string, is_json):
bid_id = bytes.fromhex(url_split[3])
assert (len(bid_id) == 28)
show_txns = False
if post_string != '':
if is_json:
post_data = json.loads(post_string)
@@ -252,6 +253,9 @@ def js_bids(self, url_split, post_string, is_json):
elif have_data_entry(post_data, 'debugind'):
swap_client.setBidDebugInd(bid_id, int(get_data_entry(post_data, 'debugind')))
if have_data_entry(post_data, 'show_extra'):
show_txns = True
bid, xmr_swap, offer, xmr_offer, events = swap_client.getXmrBidAndOffer(bid_id)
assert (bid), 'Unknown bid ID'
@@ -267,7 +271,6 @@ def js_bids(self, url_split, post_string, is_json):
return bytes(json.dumps(old_states), 'UTF-8')
edit_bid = False
show_txns = False
data = describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, events, edit_bid, show_txns, for_api=True)
return bytes(json.dumps(data), 'UTF-8')

View File

@@ -4,12 +4,18 @@
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
from basicswap.db import (
Concepts,
)
from basicswap.util import (
SerialiseNum,
)
from basicswap.script import (
OpCodes,
)
from basicswap.basicswap_util import (
EventLogTypes,
)
INITIATE_TX_TIMEOUT = 40 * 60 # TODO: make variable per coin
ABS_LOCK_TIME_LEEWAY = 10 * 60
@@ -59,3 +65,4 @@ def redeemITx(self, bid_id, session):
bid.initiate_tx.spend_txid = bytes.fromhex(txid)
self.log.debug('Submitted initiate redeem txn %s to %s chain for bid %s', txid, ci_from.coin_name(), bid_id.hex())
self.logEvent(Concepts.BID, bid_id, EventLogTypes.ITX_REDEEM_PUBLISHED, '', session)

View File

@@ -296,7 +296,7 @@ def page_wallet(self, url_split, post_string):
if show_utxo_groups:
utxo_groups = ''
unspent_by_addr = swap_client.getUnspentsByAddr(k)
unspent_by_addr = ci.getUnspentsByAddr()
sorted_unspent_by_addr = sorted(unspent_by_addr.items(), key=lambda x: x[1], reverse=True)
for kv in sorted_unspent_by_addr:

View File

@@ -356,6 +356,16 @@ def describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, bid_events, edit_b
if 'view_tx_hex' in data:
data['view_tx_desc'] = json.dumps(ci_from.describeTx(data['view_tx_hex']), indent=4)
else:
if offer.lock_type == TxLockTypes.SEQUENCE_LOCK_TIME:
if bid.initiate_tx and bid.initiate_tx.block_time is not None:
raw_sequence = ci_from.getExpectedSequence(offer.lock_type, offer.lock_value)
seconds_locked = ci_from.decodeSequence(raw_sequence)
data['itx_refund_tx_est_final'] = bid.initiate_tx.block_time + seconds_locked
if bid.participate_tx and bid.participate_tx.block_time is not None:
raw_sequence = ci_to.getExpectedSequence(offer.lock_type, offer.lock_value // 2)
seconds_locked = ci_to.decodeSequence(raw_sequence)
data['ptx_refund_tx_est_final'] = bid.participate_tx.block_time + seconds_locked
return data