2024-05-20_merge
tecnovert 4 years ago
parent acae8b4de3
commit 009729aa96
No known key found for this signature in database
GPG Key ID: 8ED6D8750C4E3F93
  1. 682
      basicswap/basicswap.py
  2. 356
      basicswap/contrib/ed25519_fast.py
  3. 80
      basicswap/db.py
  4. 36
      basicswap/ed25519_fast_util.py
  5. 79
      basicswap/interface_btc.py
  6. 48
      basicswap/interface_xmr.py
  7. 62
      basicswap/messages.proto
  8. 293
      basicswap/messages_pb2.py
  9. 1
      basicswap/util.py
  10. 20
      tests/basicswap/test_other.py
  11. 52
      tests/basicswap/test_xmr.py

@ -6,18 +6,20 @@
import os import os
import re import re
import time
import datetime as dt
import zmq import zmq
import traceback
import hashlib
import sqlalchemy as sa
import shutil
import json import json
import time
import shutil
import random import random
import logging
import secrets import secrets
from sqlalchemy.orm import sessionmaker, scoped_session import hashlib
import datetime as dt
import traceback
import sqlalchemy as sa
from enum import IntEnum, auto from enum import IntEnum, auto
from sqlalchemy.orm import sessionmaker, scoped_session
from .interface_part import PARTInterface from .interface_part import PARTInterface
from .interface_btc import BTCInterface from .interface_btc import BTCInterface
@ -39,6 +41,7 @@ from .util import (
toWIF, toWIF,
getKeyID, getKeyID,
make_int, make_int,
dumpj
) )
from .chainparams import ( from .chainparams import (
chainparams, chainparams,
@ -51,6 +54,9 @@ from .messages_pb2 import (
OfferMessage, OfferMessage,
BidMessage, BidMessage,
BidAcceptMessage, BidAcceptMessage,
XmrBidMessage,
XmrBidAcceptMessage,
XmrSplitMessage,
) )
from .db import ( from .db import (
CURRENT_DB_VERSION, CURRENT_DB_VERSION,
@ -64,6 +70,9 @@ from .db import (
SentOffer, SentOffer,
SmsgAddress, SmsgAddress,
EventQueue, EventQueue,
XmrOffer,
XmrSwap,
XmrSplitData,
) )
from .explorers import ExplorerInsight, ExplorerBitAps, ExplorerChainz from .explorers import ExplorerInsight, ExplorerBitAps, ExplorerChainz
@ -83,6 +92,9 @@ class MessageTypes(IntEnum):
BID_ACCEPT = auto() BID_ACCEPT = auto()
XMR_OFFER = auto() XMR_OFFER = auto()
XMR_BID = auto()
XMR_BID_SPLIT = auto()
XMR_BID_ACCEPT = auto()
class SwapTypes(IntEnum): class SwapTypes(IntEnum):
@ -101,7 +113,9 @@ class OfferStates(IntEnum):
class BidStates(IntEnum): class BidStates(IntEnum):
BID_SENT = auto() BID_SENT = auto()
BID_RECEIVING = auto() # Partially received
BID_RECEIVED = auto() BID_RECEIVED = auto()
BID_RECEIVING_ACC = auto() # Partially received accept message
BID_ACCEPTED = auto() # BidAcceptMessage received/sent BID_ACCEPTED = auto() # BidAcceptMessage received/sent
SWAP_INITIATED = auto() # Initiate txn validated SWAP_INITIATED = auto() # Initiate txn validated
SWAP_PARTICIPATING = auto() # Participate txn validated SWAP_PARTICIPATING = auto() # Participate txn validated
@ -132,6 +146,11 @@ class EventTypes(IntEnum):
ACCEPT_BID = auto() ACCEPT_BID = auto()
class XmrSplitMsgTypes(IntEnum):
BID = auto()
BID_ACCEPT = auto()
SEQUENCE_LOCK_BLOCKS = 1 SEQUENCE_LOCK_BLOCKS = 1
SEQUENCE_LOCK_TIME = 2 SEQUENCE_LOCK_TIME = 2
ABS_LOCK_BLOCKS = 3 ABS_LOCK_BLOCKS = 3
@ -298,10 +317,12 @@ class BasicSwap(BaseApp):
self.check_watched_seconds = self.settings.get('check_watched_seconds', 60) self.check_watched_seconds = self.settings.get('check_watched_seconds', 60)
self.check_expired_seconds = self.settings.get('check_expired_seconds', 60 * 5) self.check_expired_seconds = self.settings.get('check_expired_seconds', 60 * 5)
self.check_events_seconds = self.settings.get('check_events_seconds', 10) self.check_events_seconds = self.settings.get('check_events_seconds', 10)
self.last_checked_progress = 0 self.check_xmr_swaps_seconds = self.settings.get('check_xmr_swaps_seconds', 20)
self.last_checked_watched = 0 self._last_checked_progress = 0
self.last_checked_expired = 0 self._last_checked_watched = 0
self.last_checked_events = 0 self._last_checked_expired = 0
self._last_checked_events = 0
self._last_checked_xmr_swaps = 0
# TODO: Adjust ranges # TODO: Adjust ranges
self.min_delay_auto_accept = self.settings.get('min_delay_auto_accept', 10) self.min_delay_auto_accept = self.settings.get('min_delay_auto_accept', 10)
@ -321,7 +342,7 @@ class BasicSwap(BaseApp):
self.network_key = toWIF(wif_prefix, decodeWif(self.settings['network_key'])) self.network_key = toWIF(wif_prefix, decodeWif(self.settings['network_key']))
self.network_pubkey = self.settings['network_pubkey'] self.network_pubkey = self.settings['network_pubkey']
self.network_addr = pubkeyToAddress(chainparams[Coins.PART][self.chain]['pubkey_address'], bytearray.fromhex(self.network_pubkey)) self.network_addr = pubkeyToAddress(chainparams[Coins.PART][self.chain]['pubkey_address'], bytes.fromhex(self.network_pubkey))
#self.wallet = self.settings.get('wallet', None) # TODO: Move to coin_clients #self.wallet = self.settings.get('wallet', None) # TODO: Move to coin_clients
self.sqlite_file = os.path.join(self.data_dir, 'db{}.sqlite'.format('' if self.chain == 'mainnet' else ('_' + self.chain))) self.sqlite_file = os.path.join(self.data_dir, 'db{}.sqlite'.format('' if self.chain == 'mainnet' else ('_' + self.chain)))
@ -351,15 +372,6 @@ class BasicSwap(BaseApp):
value=self._contract_count value=self._contract_count
)) ))
session.commit() session.commit()
try:
self._offer_count = session.query(DBKVInt).filter_by(key='offer_count').first().value
except Exception:
self._offer_count = 0
session.add(DBKVInt(
key='offer_count',
value=self._offer_count
))
session.commit()
session.close() session.close()
session.remove() session.remove()
@ -450,6 +462,9 @@ class BasicSwap(BaseApp):
raise ValueError('Missing XMR wallet rpc credentials.') raise ValueError('Missing XMR wallet rpc credentials.')
self.coin_clients[coin]['interface'] = self.createInterface(coin) self.coin_clients[coin]['interface'] = self.createInterface(coin)
def ci(self, coin): # coin interface
return self.coin_clients[coin]['interface']
def createInterface(self, coin): def createInterface(self, coin):
if coin == Coins.PART: if coin == Coins.PART:
return PARTInterface(self.coin_clients[coin]) return PARTInterface(self.coin_clients[coin])
@ -741,13 +756,8 @@ class BasicSwap(BaseApp):
offer_addr = self.callrpc('getnewaddress') if addr_send_from is None else addr_send_from offer_addr = self.callrpc('getnewaddress') if addr_send_from is None else addr_send_from
offer_created_at = int(time.time()) offer_created_at = int(time.time())
if swap_type == SwapTypes.XMR_SWAP:
msg_buf = XmrOfferMessage()
key_path = "44445555h/999999/{}/{}/{}/{}".format(int(coin_from), int(coin_to), offer_created_at, )
else: msg_buf = OfferMessage()
msg_buf = OfferMessage()
msg_buf.coin_from = int(coin_from) msg_buf.coin_from = int(coin_from)
msg_buf.coin_to = int(coin_to) msg_buf.coin_to = int(coin_to)
@ -760,6 +770,18 @@ class BasicSwap(BaseApp):
msg_buf.lock_value = lock_value msg_buf.lock_value = lock_value
msg_buf.swap_type = swap_type msg_buf.swap_type = swap_type
if swap_type == SwapTypes.XMR_SWAP:
xmr_offer = XmrOffer()
xmr_offer.lock_time_1 = lock_value # Delay before the chain a lock refund tx can be mined
xmr_offer.lock_time_2 = lock_value # Delay before the follower can spend from the chain a lock refund tx
# TODO: Dynamic fee selection
xmr_offer.a_fee_rate = make_int(0.00032595, self.ci(coin_from).exp())
xmr_offer.b_fee_rate = make_int(0.0012595, self.ci(coin_to).exp())
msg_buf.fee_rate_from = xmr_offer.a_fee_rate
msg_buf.fee_rate_to = xmr_offer.b_fee_rate
offer_bytes = msg_buf.SerializeToString() offer_bytes = msg_buf.SerializeToString()
payload_hex = str.format('{:02x}', MessageTypes.OFFER) + offer_bytes.hex() payload_hex = str.format('{:02x}', MessageTypes.OFFER) + offer_bytes.hex()
@ -792,6 +814,10 @@ class BasicSwap(BaseApp):
auto_accept_bids=auto_accept_bids,) auto_accept_bids=auto_accept_bids,)
offer.setState(OfferStates.OFFER_SENT) offer.setState(OfferStates.OFFER_SENT)
if swap_type == SwapTypes.XMR_SWAP:
xmr_offer.offer_id = offer_id
session.add(xmr_offer)
session.add(offer) session.add(offer)
session.add(SentOffer(offer_id=offer_id)) session.add(SentOffer(offer_id=offer_id))
if addr_send_from is None: if addr_send_from is None:
@ -804,6 +830,31 @@ class BasicSwap(BaseApp):
self.log.info('Sent OFFER %s', offer_id.hex()) self.log.info('Sent OFFER %s', offer_id.hex())
return offer_id return offer_id
def getPathKey(self, coin_from, coin_to, offer_created_at, contract_count, key_no, for_xmr=False):
account = self.callcoinrpc(Coins.PART, 'extkey', ['account'])
evkey = self.callcoinrpc(Coins.PART, 'extkey', ['account', 'default', 'true'])['evkey']
ci = self.ci(coin_to)
days = offer_created_at // 86400
secs = offer_created_at - days * 86400
key_path_base = '44445555h/999999/{}/{}/{}/{}/{}/{}'.format(int(coin_from), int(coin_to), days, secs, contract_count, key_no)
if not for_xmr:
extkey = self.callcoinrpc(Coins.PART, 'extkey', ['info', evkey, key_path_base])['key_info']['result']
return decodeWif(self.callcoinrpc(Coins.PART, 'extkey', ['info', extkey])['key_info']['privkey'])
nonce = 1
while True:
key_path = key_path_base + '/{}'.format(nonce)
extkey = self.callcoinrpc(Coins.PART, 'extkey', ['info', evkey, key_path])['key_info']['result']
privkey = decodeWif(self.callcoinrpc(Coins.PART, 'extkey', ['info', extkey])['key_info']['privkey'])
if ci.isValidKey(privkey):
return privkey
nonce += 1
if nonce > 1000:
raise ValueError('getKeyForXMR failed')
def getContractPubkey(self, date, contract_count): def getContractPubkey(self, date, contract_count):
account = self.callcoinrpc(Coins.PART, 'extkey', ['account']) account = self.callcoinrpc(Coins.PART, 'extkey', ['account'])
@ -1005,18 +1056,31 @@ class BasicSwap(BaseApp):
return (sign_for_addr, signature) return (sign_for_addr, signature)
def saveBidInSession(self, bid_id, bid, session): def saveBidInSession(self, bid_id, bid, session, xmr_swap=None):
session.add(bid) session.add(bid)
if bid.initiate_tx: if bid.initiate_tx:
session.add(bid.initiate_tx) session.add(bid.initiate_tx)
if bid.participate_tx: if bid.participate_tx:
session.add(bid.participate_tx) session.add(bid.participate_tx)
if xmr_swap is not None:
session.add(xmr_swap)
def saveBid(self, bid_id, bid): def saveBid(self, bid_id, bid, xmr_swap=None):
self.mxDB.acquire() self.mxDB.acquire()
try: try:
session = scoped_session(self.session_factory) session = scoped_session(self.session_factory)
self.saveBidInSession(bid_id, bid, session) self.saveBidInSession(bid_id, bid, session, xmr_swap)
session.commit()
session.close()
session.remove()
finally:
self.mxDB.release()
def saveToDB(self, db_record):
self.mxDB.acquire()
try:
session = scoped_session(self.session_factory)
session.add(db_record)
session.commit() session.commit()
session.close() session.close()
session.remove() session.remove()
@ -1070,6 +1134,8 @@ class BasicSwap(BaseApp):
proof_addr, proof_sig = self.getProofOfFunds(coin_to, msg_buf.amount) proof_addr, proof_sig = self.getProofOfFunds(coin_to, msg_buf.amount)
msg_buf.proof_address = proof_addr msg_buf.proof_address = proof_addr
msg_buf.proof_signature = proof_sig msg_buf.proof_signature = proof_sig
else:
raise ValueError('TODO')
bid_bytes = msg_buf.SerializeToString() bid_bytes = msg_buf.SerializeToString()
payload_hex = str.format('{:02x}', MessageTypes.BID) + bid_bytes.hex() payload_hex = str.format('{:02x}', MessageTypes.BID) + bid_bytes.hex()
@ -1124,6 +1190,34 @@ class BasicSwap(BaseApp):
session.remove() session.remove()
self.mxDB.release() self.mxDB.release()
def getXmrBid(self, bid_id, sent=False):
self.mxDB.acquire()
try:
session = scoped_session(self.session_factory)
bid = session.query(Bid).filter_by(bid_id=bid_id).first()
xmr_swap = None
if bid:
xmr_swap = session.query(XmrSwap).filter_by(bid_id=bid_id).first()
return bid, xmr_swap
finally:
session.close()
session.remove()
self.mxDB.release()
def getXmrOffer(self, offer_id, sent=False):
self.mxDB.acquire()
try:
session = scoped_session(self.session_factory)
offer = session.query(Offer).filter_by(offer_id=offer_id).first()
xmr_offer = None
if offer:
xmr_offer = session.query(XmrOffer).filter_by(offer_id=offer_id).first()
return offer, xmr_offer
finally:
session.close()
session.remove()
self.mxDB.release()
def getBid(self, bid_id): def getBid(self, bid_id):
self.mxDB.acquire() self.mxDB.acquire()
try: try:
@ -1244,6 +1338,254 @@ class BasicSwap(BaseApp):
self.saveBid(bid_id, bid) self.saveBid(bid_id, bid)
self.swaps_in_progress[bid_id] = (bid, offer) self.swaps_in_progress[bid_id] = (bid, offer)
def postXmrBid(self, offer_id, amount, addr_send_from=None):
# Bid to send bid.amount * offer.rate of coin_to in exchange for bid.amount of coin_from
# Send MSG1L F -> L
self.log.debug('postBid %s %s', offer_id.hex(), format8(amount))
self.mxDB.acquire()
try:
offer, xmr_offer = self.getXmrOffer(offer_id)
assert(offer), 'Offer not found: {}.'.format(offer_id.hex())
assert(xmr_offer), 'XMR offer not found: {}.'.format(offer_id.hex())
assert(offer.expire_at > int(time.time())), 'Offer has expired'
msg_buf = XmrBidMessage()
msg_buf.offer_msg_id = offer_id
msg_buf.time_valid = 60 * 10
msg_buf.amount = int(amount) # amount of coin_from
coin_from = Coins(offer.coin_from)
coin_to = Coins(offer.coin_to)
ci_from = self.ci(coin_from)
ci_to = self.ci(coin_to)
self.checkSynced(coin_from, coin_to)
bid_created_at = int(time.time())
if offer.swap_type != SwapTypes.XMR_SWAP:
raise ValueError('TODO')
# Follower to leader
xmr_swap = XmrSwap()
xmr_swap.contract_count = self.getNewContractId()
xmr_swap.b_restore_height = self.ci(coin_to).getChainHeight()
kbvf = self.getPathKey(coin_from, coin_to, bid_created_at, xmr_swap.contract_count, 1, for_xmr=True)
kbsf = self.getPathKey(coin_from, coin_to, bid_created_at, xmr_swap.contract_count, 2, for_xmr=True)
kaf = self.getPathKey(coin_from, coin_to, bid_created_at, xmr_swap.contract_count, 3)
karf = self.getPathKey(coin_from, coin_to, bid_created_at, xmr_swap.contract_count, 4)
xmr_swap.pkaf = ci_from.getPubkey(kaf)
xmr_swap.pkarf = ci_from.getPubkey(karf)
xmr_swap.kbsf_dleag = ci_to.proveDLEAG(kbsf)
msg_buf.pkaf = xmr_swap.pkaf
msg_buf.pkarf = xmr_swap.pkarf
msg_buf.kbvf = kbvf
msg_buf.kbsf_dleag = xmr_swap.kbsf_dleag[:16000]
bid_bytes = msg_buf.SerializeToString()
payload_hex = str.format('{:02x}', MessageTypes.XMR_BID) + bid_bytes.hex()
if addr_send_from is None:
bid_addr = self.callrpc('getnewaddress')
else:
bid_addr = addr_send_from
self.callrpc('smsgaddlocaladdress', [bid_addr]) # Enable receiving smsg
options = {'decodehex': True, 'ttl_is_seconds': True}
msg_valid = self.SMSG_SECONDS_IN_HOUR * 1
ro = self.callrpc('smsgsend', [bid_addr, offer.addr_from, payload_hex, False, msg_valid, False, options])
xmr_swap.bid_id = bytes.fromhex(ro['msgid'])
msg_buf2 = XmrSplitMessage(
msg_id=xmr_swap.bid_id,
msg_type=XmrSplitMsgTypes.BID,
sequence=2,
dleag=xmr_swap.kbsf_dleag[16000:32000]
)
msg_bytes = msg_buf2.SerializeToString()
payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_SPLIT) + msg_bytes.hex()
ro = self.callrpc('smsgsend', [bid_addr, offer.addr_from, payload_hex, False, msg_valid, False, options])
xmr_swap.bid_msg_id2 = bytes.fromhex(ro['msgid'])
msg_buf3 = XmrSplitMessage(
msg_id=xmr_swap.bid_id,
msg_type=XmrSplitMsgTypes.BID,
sequence=3,
dleag=xmr_swap.kbsf_dleag[32000:]
)
msg_bytes = msg_buf3.SerializeToString()
payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_SPLIT) + msg_bytes.hex()
ro = self.callrpc('smsgsend', [bid_addr, offer.addr_from, payload_hex, False, msg_valid, False, options])
xmr_swap.bid_msg_id3 = bytes.fromhex(ro['msgid'])
bid = Bid(
bid_id=xmr_swap.bid_id,
offer_id=offer_id,
amount=msg_buf.amount,
#pkhash_buyer=msg_buf.pkhash_buyer,
#proof_address=msg_buf.proof_address,
created_at=bid_created_at,
contract_count=xmr_swap.contract_count,
amount_to=(msg_buf.amount * offer.rate) // COIN,
expire_at=bid_created_at + msg_buf.time_valid,
bid_addr=bid_addr,
was_sent=True,
)
bid.setState(BidStates.BID_SENT)
session = scoped_session(self.session_factory)
self.saveBidInSession(xmr_swap.bid_id, bid, session, xmr_swap)
if addr_send_from is None:
session.add(SmsgAddress(addr=bid_addr, use_type=MessageTypes.BID))
session.commit()
session.close()
session.remove()
self.log.info('Sent XMR_BID %s', xmr_swap.bid_id.hex())
return xmr_swap.bid_id
finally:
self.mxDB.release()
def acceptXmrBid(self, bid_id):
# MSG1F and MSG2F L -> F
self.log.info('Accepting xmr bid %s', bid_id.hex())
now = int(time.time())
self.mxDB.acquire()
try:
bid, xmr_swap = self.getXmrBid(bid_id)
assert(bid), 'Bid not found: {}.'.format(bid_id.hex())
assert(xmr_swap), 'XMR swap not found: {}.'.format(bid_id.hex())
assert(bid.expire_at > now), 'Offer has expired'
offer, xmr_offer = self.getXmrOffer(bid.offer_id)
assert(offer), 'Offer not found: {}.'.format(bid.offer_id.hex())
assert(xmr_offer), 'XMR offer not found: {}.'.format(bid.offer_id.hex())
assert(offer.expire_at > now), 'Offer has expired'
coin_from = Coins(offer.coin_from)
coin_to = Coins(offer.coin_to)
ci_from = self.ci(coin_from)
ci_to = self.ci(coin_to)
self.log.debug('[rm] acceptXmrBid bid.created_at %d', bid.created_at)
if xmr_swap.contract_count is None:
xmr_swap.contract_count = self.getNewContractId()
contract_secret = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 1)
kbvl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 2, for_xmr=True)
kbsl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 3, for_xmr=True)
kal = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 4)
karl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 5)
xmr_swap.sh = hashlib.sha256(contract_secret).digest()
xmr_swap.pkal = ci_from.getPubkey(kal)
xmr_swap.pkarl = ci_from.getPubkey(karl)
xmr_swap.kbsl_dleag = ci_to.proveDLEAG(kbsl)
# MSG2F
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script = ci_from.createScriptLockTx(
bid.amount,
xmr_swap.sh,
xmr_swap.pkal, xmr_swap.pkaf,
xmr_offer.lock_time_1,
xmr_swap.pkarl, xmr_swap.pkarf,
)
xmr_swap.a_lock_tx = ci_from.fundTx(xmr_swap.a_lock_tx, xmr_offer.a_fee_rate)
xmr_swap.a_lock_tx_id = ci_from.getTxHash(xmr_swap.a_lock_tx)
xmr_swap.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_tx, xmr_swap.a_lock_tx_script,
xmr_swap.pkarl, xmr_swap.pkarf,
xmr_offer.lock_time_2,
xmr_swap.pkaf,
xmr_offer.a_fee_rate
)
xmr_swap.al_lock_refund_tx_sig = ci_from.signTx(karl, xmr_swap.a_lock_refund_tx, 0, xmr_swap.a_lock_tx_script, bid.amount)
v = ci_from.verifyTxSig(xmr_swap.a_lock_refund_tx, xmr_swap.al_lock_refund_tx_sig, xmr_swap.pkarl, 0, xmr_swap.a_lock_tx_script, bid.amount)
assert(v)
xmr_swap.a_lock_refund_spend_tx = ci_from.createScriptLockRefundSpendTx(
xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script,
xmr_swap.pkal,
xmr_offer.a_fee_rate
)
msg_buf = XmrBidAcceptMessage()
msg_buf.bid_msg_id = bid_id
msg_buf.sh = xmr_swap.sh
msg_buf.pkal = xmr_swap.pkal
msg_buf.pkarl = xmr_swap.pkarl
msg_buf.kbvl = kbvl
msg_buf.kbsl_dleag = xmr_swap.kbsl_dleag[:16000]
# MSG2F
msg_buf.a_lock_tx = xmr_swap.a_lock_tx
msg_buf.a_lock_tx_script = xmr_swap.a_lock_tx_script
msg_buf.a_lock_refund_tx = xmr_swap.a_lock_refund_tx
msg_buf.a_lock_refund_tx_script = xmr_swap.a_lock_refund_tx_script
msg_buf.a_lock_refund_spend_tx = xmr_swap.a_lock_refund_spend_tx
msg_buf.al_lock_refund_tx_sig = xmr_swap.al_lock_refund_tx_sig
msg_bytes = msg_buf.SerializeToString()
self.log.debug('[rm] acceptXmrBid len(msg_bytes) %d', len(msg_bytes))
payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_ACCEPT) + msg_bytes.hex()
options = {'decodehex': True, 'ttl_is_seconds': True}
msg_valid = self.SMSG_SECONDS_IN_HOUR * 48
ro = self.callrpc('smsgsend', [offer.addr_from, bid.bid_addr, payload_hex, False, msg_valid, False, options])
msg_id = ro['msgid']
bid.accept_msg_id = bytes.fromhex(msg_id)
xmr_swap.bid_accept_msg_id = bid.accept_msg_id
msg_buf2 = XmrSplitMessage(
msg_id=bid_id,
msg_type=XmrSplitMsgTypes.BID_ACCEPT,
sequence=2,
dleag=xmr_swap.kbsl_dleag[16000:32000]
)
msg_bytes = msg_buf2.SerializeToString()
payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_SPLIT) + msg_bytes.hex()
ro = self.callrpc('smsgsend', [offer.addr_from, bid.bid_addr, payload_hex, False, msg_valid, False, options])
xmr_swap.bid_accept_msg_id2 = bytes.fromhex(ro['msgid'])
msg_buf3 = XmrSplitMessage(
msg_id=bid_id,
msg_type=XmrSplitMsgTypes.BID_ACCEPT,
sequence=3,
dleag=xmr_swap.kbsl_dleag[32000:]
)
msg_bytes = msg_buf3.SerializeToString()
payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_SPLIT) + msg_bytes.hex()
ro = self.callrpc('smsgsend', [offer.addr_from, bid.bid_addr, payload_hex, False, msg_valid, False, options])
xmr_swap.bid_accept_msg_id3 = bytes.fromhex(ro['msgid'])
bid.setState(BidStates.BID_ACCEPTED)
session = scoped_session(self.session_factory)
self.saveBidInSession(bid_id, bid, session, xmr_swap)
session.commit()
session.close()
session.remove()
self.swaps_in_progress[bid_id] = (bid, offer)
self.log.info('Sent XMR_BID_ACCEPT %s', bid_id.hex())
return bid_id
finally:
self.mxDB.release()
def abandonOffer(self, offer_id): def abandonOffer(self, offer_id):
self.log.info('Abandoning Offer %s', offer_id.hex()) self.log.info('Abandoning Offer %s', offer_id.hex())
self.mxDB.acquire() self.mxDB.acquire()
@ -1282,10 +1624,10 @@ class BasicSwap(BaseApp):
session.remove() session.remove()
self.mxDB.release() self.mxDB.release()
def setBidError(self, bif_id, bid, error_str): def setBidError(self, bid_id, bid, error_str):
bid.setState(BidStates.BID_ERROR) bid.setState(BidStates.BID_ERROR)
bid.state_note = 'error msg: ' + error_str bid.state_note = 'error msg: ' + error_str
self.saveBid(bif_id, bid) self.saveBid(bid_id, bid)
def createInitiateTxn(self, coin_type, bid_id, bid, initiate_script): def createInitiateTxn(self, coin_type, bid_id, bid, initiate_script):
if self.coin_clients[coin_type]['connection_type'] != 'rpc': if self.coin_clients[coin_type]['connection_type'] != 'rpc':
@ -1771,6 +2113,9 @@ class BasicSwap(BaseApp):
return sum_unspent return sum_unspent
return None return None
def checkXmrBidState(self, bid_id, bid, offer):
pass
def checkBidState(self, bid_id, bid, offer): def checkBidState(self, bid_id, bid, offer):
# assert(self.mxDB.locked()) # assert(self.mxDB.locked())
# Return True to remove bid from in-progress list # Return True to remove bid from in-progress list
@ -1778,6 +2123,9 @@ class BasicSwap(BaseApp):
state = BidStates(bid.state) state = BidStates(bid.state)
self.log.debug('checkBidState %s %s', bid_id.hex(), str(state)) self.log.debug('checkBidState %s %s', bid_id.hex(), str(state))
if offer.swap_type == SwapTypes.XMR_SWAP:
return self.checkXmrBidState(bid_id, bid, offer)
save_bid = False save_bid = False
coin_from = Coins(offer.coin_from) coin_from = Coins(offer.coin_from)
coin_to = Coins(offer.coin_to) coin_to = Coins(offer.coin_to)
@ -2072,21 +2420,19 @@ class BasicSwap(BaseApp):
options = {'encoding': 'none', 'delete': True} options = {'encoding': 'none', 'delete': True}
del_msg = self.callrpc('smsg', [msg['msgid'], options]) del_msg = self.callrpc('smsg', [msg['msgid'], options])
# TODO: remove offers from db logging.debug('TODO: Expire records from db')
finally: finally:
self.mxDB.release() self.mxDB.release()
def checkEvents(self): def checkEvents(self):
self.mxDB.acquire() self.mxDB.acquire()
now = int(time.time())
try: try:
now = int(time.time())
session = scoped_session(self.session_factory) session = scoped_session(self.session_factory)
q = session.query(EventQueue).filter(EventQueue.trigger_at >= now) q = session.query(EventQueue).filter(EventQueue.trigger_at >= now)
for row in q: for row in q:
if row.event_type == EventTypes.ACCEPT_BID: if row.event_type == EventTypes.ACCEPT_BID:
self.acceptBid(row.linked_id) self.acceptBid(row.linked_id)
else: else:
@ -2100,6 +2446,55 @@ class BasicSwap(BaseApp):
finally: finally:
self.mxDB.release() self.mxDB.release()
def checkXmrSwaps(self):
self.mxDB.acquire()
now = int(time.time())
ttl_xmr_split_messages = 60 * 60
try:
session = scoped_session(self.session_factory)
q = session.query(Bid).filter(Bid.state == BidStates.BID_RECEIVING)
for bid in q:
q = self.engine.execute('SELECT COUNT(*) FROM xmr_split_data WHERE bid_id = x\'{}\' AND msg_type = {}'.format(bid.bid_id.hex(), XmrSplitMsgTypes.BID)).first()
num_segments = q[0]
if num_segments > 1:
try:
self.receiveXmrBid(bid, session)
except Exception as e:
self.log.info('Verify xmr bid {} failed: {}'.format(bid.bid_id.hex(), str(e)))
bid.setState(BidStates.BID_ERROR, 'Failed validation: ' + str(e))
session.add(bid)
continue
if bid.created_at + ttl_xmr_split_messages < now:
self.log.debug('Expiring partially received bid: {}'.format(bid.bid_id.hex()))
bid.setState(BidStates.BID_ERROR, 'Timed out')
session.add(bid)
q = session.query(Bid).filter(Bid.state == BidStates.BID_RECEIVING_ACC)
for bid in q:
q = self.engine.execute('SELECT COUNT(*) FROM xmr_split_data WHERE bid_id = x\'{}\' AND msg_type = {}'.format(bid.bid_id.hex(), XmrSplitMsgTypes.BID_ACCEPT)).first()
num_segments = q[0]
if num_segments > 1:
try:
self.receiveXmrBidAccept(bid, session)
except Exception as e:
self.log.info('Verify xmr bid accept {} failed: {}'.format(bid.bid_id.hex(), str(e)))
bid.setState(BidStates.BID_ERROR, 'Failed accept validation: ' + str(e))
session.add(bid)
continue
if bid.created_at + ttl_xmr_split_messages < now:
self.log.debug('Expiring partially received bid accept: {}'.format(bid.bid_id.hex()))
bid.setState(BidStates.BID_ERROR, 'Timed out')
session.add(bid)
# Expire old records
q = session.query(XmrSplitData).filter(XmrSplitData.created_at + ttl_xmr_split_messages < now)
q.delete(synchronize_session=False)
session.commit()
session.close()
session.remove()
finally:
self.mxDB.release()
def processOffer(self, msg): def processOffer(self, msg):
assert(msg['to'] == self.network_addr), 'Offer received on wrong address' assert(msg['to'] == self.network_addr), 'Offer received on wrong address'
@ -2159,6 +2554,19 @@ class BasicSwap(BaseApp):
was_sent=False) was_sent=False)
offer.setState(OfferStates.OFFER_RECEIVED) offer.setState(OfferStates.OFFER_RECEIVED)
session.add(offer) session.add(offer)
if offer.swap_type == SwapTypes.XMR_SWAP:
xmr_offer = XmrOffer()
xmr_offer.offer_id = offer_id
xmr_offer.lock_time_1 = offer_data.lock_value
xmr_offer.lock_time_2 = offer_data.lock_value
xmr_offer.a_fee_rate = offer_data.fee_rate_from
xmr_offer.b_fee_rate = offer_data.fee_rate_to
session.add(xmr_offer)
self.log.debug('Received new offer %s', offer_id.hex()) self.log.debug('Received new offer %s', offer_id.hex())
else: else:
existing_offer.setState(OfferStates.OFFER_RECEIVED) existing_offer.setState(OfferStates.OFFER_RECEIVED)
@ -2326,6 +2734,179 @@ class BasicSwap(BaseApp):
self.saveBid(bid_id, bid) self.saveBid(bid_id, bid)
self.swaps_in_progress[bid_id] = (bid, offer) self.swaps_in_progress[bid_id] = (bid, offer)
def receiveXmrBid(self, bid, session):
self.log.debug('Receiving xmr bid %s', bid.bid_id.hex())
now = int(time.time())
offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=True)
assert(offer and offer.was_sent), 'Offer not found: {}.'.format(offer_id.hex())
assert(xmr_offer), 'XMR offer not found: {}.'.format(offer_id.hex())
coin_from = Coins(offer.coin_from)
coin_to = Coins(offer.coin_to)
xmr_swap = session.query(XmrSwap).filter_by(bid_id=bid.bid_id).first()
assert(xmr_swap), 'XMR swap not found: {}.'.format(bid.bid_id.hex())
ci_from = self.ci(coin_from)
ci_to = self.ci(coin_to)
if len(xmr_swap.kbsf_dleag) < ci_to.lengthDLEAG():
q = session.query(XmrSplitData).filter(sa.and_(XmrSplitData.bid_id == bid.bid_id, XmrSplitData.msg_type == XmrSplitMsgTypes.BID)).order_by(XmrSplitData.msg_sequence.asc())
for row in q:
xmr_swap.kbsf_dleag += row.dleag
if not ci_to.verifyKey(xmr_swap.vkbvf):
raise ValueError('Invalid key.')
if not ci_to.verifyDLEAG(xmr_swap.kbsf_dleag):
raise ValueError('Invalid DLEAG proof.')
if not ci_from.verifyPubkey(xmr_swap.pkaf):
raise ValueError('Invalid pubkey.')
if not ci_from.verifyPubkey(xmr_swap.pkarf):
raise ValueError('Invalid pubkey.')
bid.setState(BidStates.BID_RECEIVED)
self.saveBidInSession(bid.bid_id, bid, session, xmr_swap)
def receiveXmrBidAccept(self, bid, session):
self.log.debug('Receiving xmr bid accept %s', bid.bid_id.hex())
now = int(time.time())
offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=True)
assert(offer), 'Offer not found: {}.'.format(bid.offer_id.hex())
assert(xmr_offer), 'XMR offer not found: {}.'.format(bid.offer_id.hex())
coin_from = Coins(offer.coin_from)
coin_to = Coins(offer.coin_to)
xmr_swap = session.query(XmrSwap).filter_by(bid_id=bid.bid_id).first()
assert(xmr_swap), 'XMR swap not found: {}.'.format(bid.bid_id.hex())
ci_from = self.ci(coin_from)
ci_to = self.ci(coin_to)
if len(xmr_swap.kbsl_dleag) < ci_to.lengthDLEAG():
q = session.query(XmrSplitData).filter(sa.and_(XmrSplitData.bid_id == bid.bid_id, XmrSplitData.msg_type == XmrSplitMsgTypes.BID_ACCEPT)).order_by(XmrSplitData.msg_sequence.asc())
for row in q:
xmr_swap.kbsl_dleag += row.dleag
if not ci_to.verifyKey(xmr_swap.vkbvl):
raise ValueError('Invalid key.')
if not ci_to.verifyDLEAG(xmr_swap.kbsl_dleag):
raise ValueError('Invalid DLEAG proof.')
if not ci_from.verifyPubkey(xmr_swap.pkal):
raise ValueError('Invalid pubkey.')
if not ci_from.verifyPubkey(xmr_swap.pkarl):
raise ValueError('Invalid pubkey.')
bid.setState(BidStates.BID_ACCEPTED)
self.saveBidInSession(bid.bid_id, bid, session, xmr_swap)
def processXmrBid(self, msg):
self.log.debug('Processing xmr bid msg %s', msg['msgid'])
now = int(time.time())
bid_bytes = bytes.fromhex(msg['hex'][2:-2])
bid_data = XmrBidMessage()
bid_data.ParseFromString(bid_bytes)
# Validate data
assert(len(bid_data.offer_msg_id) == 28), 'Bad offer_id length'
assert(bid_data.time_valid >= MIN_BID_VALID_TIME and bid_data.time_valid <= MAX_BID_VALID_TIME), 'Invalid time_valid'
offer_id = bid_data.offer_msg_id
offer, xmr_offer = self.getXmrOffer(offer_id, sent=True)
assert(offer and offer.was_sent), 'Offer not found: {}.'.format(offer_id.hex())
assert(xmr_offer), 'XMR offer not found: {}.'.format(offer_id.hex())
coin_from = Coins(offer.coin_from)
coin_to = Coins(offer.coin_to)
logging.debug('TODO: xmr bid validation')
bid_id = bytes.fromhex(msg['msgid'])
bid, xmr_swap = self.getXmrBid(bid_id)
if bid is None:
bid = Bid(
bid_id=bid_id,
offer_id=offer_id,
amount=bid_data.amount,
#pkhash_buyer=bid_data.pkhash_buyer,
created_at=msg['sent'],
amount_to=(bid_data.amount * offer.rate) // COIN,
expire_at=msg['sent'] + bid_data.time_valid,
bid_addr=msg['from'],
was_received=True,
)
xmr_swap = XmrSwap(
bid_id=bid_id,
pkaf=bid_data.pkaf,
pkarf=bid_data.pkarf,
vkbvf=bid_data.kbvf,
kbsf_dleag=bid_data.kbsf_dleag,
b_restore_height=self.ci(coin_to).getChainHeight(),
)
else:
bid.created_at = msg['sent']
bid.expire_at = msg['sent'] + bid_data.time_valid
bid.was_received = True
bid.setState(BidStates.BID_RECEIVING)
self.log.info('Receiving xmr bid %s for offer %s', bid_id.hex(), bid_data.offer_msg_id.hex())
self.saveBid(bid_id, bid, xmr_swap=xmr_swap)
def processXmrBidAccept(self, msg):
# F receiving MSG1F
self.log.debug('Processing xmr bid accept msg %s', msg['msgid'])
now = int(time.time())
msg_bytes = bytes.fromhex(msg['hex'][2:-2])
msg_data = XmrBidAcceptMessage()
msg_data.ParseFromString(msg_bytes)
assert(len(msg_data.bid_msg_id) == 28), 'Bad bid_msg_id length'
self.log.debug('for bid %s', msg_data.bid_msg_id.hex())
bid, xmr_swap = self.getXmrBid(msg_data.bid_msg_id)
offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=True)
assert(offer), 'Offer not found: {}.'.format(bid.offer_id.hex())
assert(xmr_offer), 'XMR offer not found: {}.'.format(bid.offer_id.hex())
coin_from = Coins(offer.coin_from)
coin_to = Coins(offer.coin_to)
assert(len(msg_data.sh) == 32), 'Bad secret hash length'
xmr_swap.sh = msg_data.sh
xmr_swap.pkal = msg_data.pkal
xmr_swap.pkarl = msg_data.pkarl
xmr_swap.vkbvl = msg_data.kbvl
xmr_swap.kbsl_dleag = msg_data.kbsl_dleag
bid.setState(BidStates.BID_RECEIVING_ACC)
self.saveBid(msg_data.bid_msg_id, bid, xmr_swap=xmr_swap)
def processXmrSplitMessage(self, msg):
self.log.debug('Processing xmr split msg %s', msg['msgid'])
now = int(time.time())
msg_bytes = bytes.fromhex(msg['hex'][2:-2])
msg_data = XmrSplitMessage()
msg_data.ParseFromString(msg_bytes)
# Validate data
print('[rm] msg_data.msg_id', msg_data.msg_id.hex())
assert(len(msg_data.msg_id) == 28), 'Bad msg_id length'
if msg_data.msg_type == XmrSplitMsgTypes.BID or msg_data.msg_type == XmrSplitMsgTypes.BID_ACCEPT:
dbr = XmrSplitData()
dbr.bid_id = msg_data.msg_id
dbr.msg_type = msg_data.msg_type
dbr.msg_sequence = msg_data.sequence
dbr.dleag = msg_data.dleag
dbr.created_at = now
self.saveToDB(dbr)
def processMsg(self, msg): def processMsg(self, msg):
self.mxDB.acquire() self.mxDB.acquire()
try: try:
@ -2338,6 +2919,12 @@ class BasicSwap(BaseApp):
self.processBid(msg) self.processBid(msg)
elif msg_type == MessageTypes.BID_ACCEPT: elif msg_type == MessageTypes.BID_ACCEPT:
self.processBidAccept(msg) self.processBidAccept(msg)
elif msg_type == MessageTypes.XMR_BID:
self.processXmrBid(msg)
elif msg_type == MessageTypes.XMR_BID_ACCEPT:
self.processXmrBidAccept(msg)
elif msg_type == MessageTypes.XMR_BID_SPLIT:
self.processXmrSplitMessage(msg)
except Exception as ex: except Exception as ex:
self.log.error('processMsg %s', str(ex)) self.log.error('processMsg %s', str(ex))
@ -2373,7 +2960,7 @@ class BasicSwap(BaseApp):
try: try:
# TODO: Wait for blocks / txns, would need to check multiple coins # TODO: Wait for blocks / txns, would need to check multiple coins
now = int(time.time()) now = int(time.time())
if now - self.last_checked_progress >= self.check_progress_seconds: if now - self._last_checked_progress >= self.check_progress_seconds:
to_remove = [] to_remove = []
for bid_id, v in self.swaps_in_progress.items(): for bid_id, v in self.swaps_in_progress.items():
try: try:
@ -2387,21 +2974,25 @@ class BasicSwap(BaseApp):
for bid_id in to_remove: for bid_id in to_remove:
self.log.debug('Removing bid from in-progress: %s', bid_id.hex()) self.log.debug('Removing bid from in-progress: %s', bid_id.hex())
del self.swaps_in_progress[bid_id] del self.swaps_in_progress[bid_id]
self.last_checked_progress = now self._last_checked_progress = now
if now - self.last_checked_watched >= self.check_watched_seconds: if now - self._last_checked_watched >= self.check_watched_seconds:
for k, c in self.coin_clients.items(): for k, c in self.coin_clients.items():
if len(c['watched_outputs']) > 0: if len(c['watched_outputs']) > 0:
self.checkForSpends(k, c) self.checkForSpends(k, c)
self.last_checked_watched = now self._last_checked_watched = now
if now - self.last_checked_expired >= self.check_expired_seconds: if now - self._last_checked_expired >= self.check_expired_seconds:
self.expireMessages() self.expireMessages()
self.last_checked_expired = now self._last_checked_expired = now
if now - self.last_checked_events >= self.check_events_seconds: if now - self._last_checked_events >= self.check_events_seconds:
self.checkEvents() self.checkEvents()
self.last_checked_events = now self._last_checked_events = now
if now - self._last_checked_xmr_swaps >= self.check_xmr_swaps_seconds:
self.checkXmrSwaps()
self._last_checked_xmr_swaps = now
except Exception as ex: except Exception as ex:
self.log.error('update %s', str(ex)) self.log.error('update %s', str(ex))
@ -2542,6 +3133,9 @@ class BasicSwap(BaseApp):
else: else:
q = session.query(Offer).filter(Offer.expire_at > now) q = session.query(Offer).filter(Offer.expire_at > now)
filter_offer_id = filters.get('offer_id', None)
if filter_offer_id is not None:
q = q.filter(Offer.offer_id == filter_offer_id)
filter_coin_from = filters.get('coin_from', None) filter_coin_from = filters.get('coin_from', None)
if filter_coin_from and filter_coin_from > -1: if filter_coin_from and filter_coin_from > -1:
q = q.filter(Offer.coin_from == int(filter_coin_from)) q = q.filter(Offer.coin_from == int(filter_coin_from))

@ -0,0 +1,356 @@
# ed25519.py - Optimized version of the reference implementation of Ed25519
#
# Written in 2011? by Daniel J. Bernstein <djb@cr.yp.to>
# 2013 by Donald Stufft <donald@stufft.io>
# 2013 by Alex Gaynor <alex.gaynor@gmail.com>
# 2013 by Greg Price <price@mit.edu>
#
# To the extent possible under law, the author(s) have dedicated all copyright
# and related and neighboring rights to this software to the public domain
# worldwide. This software is distributed without any warranty.
#
# You should have received a copy of the CC0 Public Domain Dedication along
# with this software. If not, see
# <http://creativecommons.org/publicdomain/zero/1.0/>.
"""
NB: This code is not safe for use with secret keys or secret data.
The only safe use of this code is for verifying signatures on public messages.
Functions for computing the public key of a secret key and for signing
a message are included, namely publickey_unsafe and signature_unsafe,
for testing purposes only.
The root of the problem is that Python's long-integer arithmetic is
not designed for use in cryptography. Specifically, it may take more
or less time to execute an operation depending on the values of the
inputs, and its memory access patterns may also depend on the inputs.
This opens it to timing and cache side-channel attacks which can
disclose data to an attacker. We rely on Python's long-integer
arithmetic, so we cannot handle secrets without risking their disclosure.
"""
import hashlib
import operator
import sys
__version__ = "1.0.dev0"
# Useful for very coarse version differentiation.
PY3 = sys.version_info[0] == 3
if PY3:
indexbytes = operator.getitem
intlist2bytes = bytes
int2byte = operator.methodcaller("to_bytes", 1, "big")
else:
int2byte = chr
range = xrange
def indexbytes(buf, i):
return ord(buf[i])
def intlist2bytes(l):
return b"".join(chr(c) for c in l)
b = 256
q = 2 ** 255 - 19
l = 2 ** 252 + 27742317777372353535851937790883648493
def H(m):
return hashlib.sha512(m).digest()
def pow2(x, p):
"""== pow(x, 2**p, q)"""
while p > 0:
x = x * x % q
p -= 1
return x
def inv(z):
"""$= z^{-1} \mod q$, for z != 0"""
# Adapted from curve25519_athlon.c in djb's Curve25519.
z2 = z * z % q # 2
z9 = pow2(z2, 2) * z % q # 9
z11 = z9 * z2 % q # 11
z2_5_0 = (z11 * z11) % q * z9 % q # 31 == 2^5 - 2^0
z2_10_0 = pow2(z2_5_0, 5) * z2_5_0 % q # 2^10 - 2^0
z2_20_0 = pow2(z2_10_0, 10) * z2_10_0 % q # ...
z2_40_0 = pow2(z2_20_0, 20) * z2_20_0 % q
z2_50_0 = pow2(z2_40_0, 10) * z2_10_0 % q
z2_100_0 = pow2(z2_50_0, 50) * z2_50_0 % q
z2_200_0 = pow2(z2_100_0, 100) * z2_100_0 % q
z2_250_0 = pow2(z2_200_0, 50) * z2_50_0 % q # 2^250 - 2^0
return pow2(z2_250_0, 5) * z11 % q # 2^255 - 2^5 + 11 = q - 2
d = -121665 * inv(121666) % q
I = pow(2, (q - 1) // 4, q)
def xrecover(y, sign=0):
xx = (y * y - 1) * inv(d * y * y + 1)
x = pow(xx, (q + 3) // 8, q)
if (x * x - xx) % q != 0:
x = (x * I) % q
if x % 2 != sign:
x = q-x
return x
By = 4 * inv(5)
Bx = xrecover(By)
B = (Bx % q, By % q, 1, (Bx * By) % q)
ident = (0, 1, 1, 0)
def edwards_add(P, Q):
# This is formula sequence 'addition-add-2008-hwcd-3' from
# http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html
(x1, y1, z1, t1) = P
(x2, y2, z2, t2) = Q
a = (y1-x1)*(y2-x2) % q
b = (y1+x1)*(y2+x2) % q
c = t1*2*d*t2 % q
dd = z1*2*z2 % q
e = b - a
f = dd - c
g = dd + c
h = b + a
x3 = e*f
y3 = g*h
t3 = e*h
z3 = f*g
return (x3 % q, y3 % q, z3 % q, t3 % q)
def edwards_sub(P, Q):
# This is formula sequence 'addition-add-2008-hwcd-3' from
# http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html
(x1, y1, z1, t1) = P
(x2, y2, z2, t2) = Q
# https://eprint.iacr.org/2008/522.pdf
# The negative of (X:Y:Z)is (−X:Y:Z)
#x2 = q-x2
"""
doesn't work
x2 = q-x2
t2 = (x2*y2) % q
"""
zi = inv(z2)
x2 = q-((x2 * zi) % q)
y2 = (y2 * zi) % q
z2 = 1
t2 = (x2*y2) % q
a = (y1-x1)*(y2-x2) % q
b = (y1+x1)*(y2+x2) % q
c = t1*2*d*t2 % q
dd = z1*2*z2 % q
e = b - a
f = dd - c
g = dd + c
h = b + a
x3 = e*f
y3 = g*h
t3 = e*h
z3 = f*g
return (x3 % q, y3 % q, z3 % q, t3 % q)
def edwards_double(P):
# This is formula sequence 'dbl-2008-hwcd' from
# http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html
(x1, y1, z1, t1) = P
a = x1*x1 % q
b = y1*y1 % q
c = 2*z1*z1 % q
# dd = -a
e = ((x1+y1)*(x1+y1) - a - b) % q
g = -a + b # dd + b
f = g - c
h = -a - b # dd - b
x3 = e*f
y3 = g*h
t3 = e*h
z3 = f*g
return (x3 % q, y3 % q, z3 % q, t3 % q)
def scalarmult(P, e):
if e == 0:
return ident
Q = scalarmult(P, e // 2)
Q = edwards_double(Q)
if e & 1:
Q = edwards_add(Q, P)
return Q
# Bpow[i] == scalarmult(B, 2**i)
Bpow = []
def make_Bpow():
P = B
for i in range(253):
Bpow.append(P)
P = edwards_double(P)
make_Bpow()
def scalarmult_B(e):
"""
Implements scalarmult(B, e) more efficiently.
"""
# scalarmult(B, l) is the identity
e = e % l
P = ident
for i in range(253):
if e & 1:
P = edwards_add(P, Bpow[i])
e = e // 2
assert e == 0, e
return P
def encodeint(y):
bits = [(y >> i) & 1 for i in range(b)]
return b''.join([
int2byte(sum([bits[i * 8 + j] << j for j in range(8)]))
for i in range(b//8)
])
def encodepoint(P):
(x, y, z, t) = P
zi = inv(z)
x = (x * zi) % q
y = (y * zi) % q
bits = [(y >> i) & 1 for i in range(b - 1)] + [x & 1]
return b''.join([
int2byte(sum([bits[i * 8 + j] << j for j in range(8)]))
for i in range(b // 8)
])
def bit(h, i):
return (indexbytes(h, i // 8) >> (i % 8)) & 1
def publickey_unsafe(sk):
"""
Not safe to use with secret keys or secret data.
See module docstring. This function should be used for testing only.
"""
h = H(sk)
a = 2 ** (b - 2) + sum(2 ** i * bit(h, i) for i in range(3, b - 2))
A = scalarmult_B(a)
return encodepoint(A)
def Hint(m):
h = H(m)
return sum(2 ** i * bit(h, i) for i in range(2 * b))
def signature_unsafe(m, sk, pk):
"""
Not safe to use with secret keys or secret data.
See module docstring. This function should be used for testing only.
"""
h = H(sk)
a = 2 ** (b - 2) + sum(2 ** i * bit(h, i) for i in range(3, b - 2))
r = Hint(
intlist2bytes([indexbytes(h, j) for j in range(b // 8, b // 4)]) + m
)
R = scalarmult_B(r)
S = (r + Hint(encodepoint(R) + pk + m) * a) % l
return encodepoint(R) + encodeint(S)
def isoncurve(P):
(x, y, z, t) = P
return (z % q != 0 and
x*y % q == z*t % q and
(y*y - x*x - z*z - d*t*t) % q == 0)
def decodeint(s):
return sum(2 ** i * bit(s, i) for i in range(0, b))
def decodepoint(s):
y = sum(2 ** i * bit(s, i) for i in range(0, b - 1))
x = xrecover(y)
if x & 1 != bit(s, b-1):
x = q - x
P = (x, y, 1, (x*y) % q)
if not isoncurve(P):
raise ValueError("decoding point that is not on curve")
return P
class SignatureMismatch(Exception):
pass
def checkvalid(s, m, pk):
"""
Not safe to use when any argument is secret.
See module docstring. This function should be used only for
verifying public signatures of public messages.
"""
if len(s) != b // 4:
raise ValueError("signature length is wrong")
if len(pk) != b // 8:
raise ValueError("public-key length is wrong")
R = decodepoint(s[:b // 8])
A = decodepoint(pk)
S = decodeint(s[b // 8:b // 4])
h = Hint(encodepoint(R) + pk + m)
(x1, y1, z1, t1) = P = scalarmult_B(S)
(x2, y2, z2, t2) = Q = edwards_add(R, scalarmult(A, h))
if (not isoncurve(P) or not isoncurve(Q) or
(x1*z2 - x2*z1) % q != 0 or (y1*z2 - y2*z1) % q != 0):
raise SignatureMismatch("signature does not pass verification")
def is_identity(P):
return True if P[0] == 0 else False
def edwards_negated(P):
(x, y, z, t) = P
zi = inv(z)
x = q - ((x * zi) % q)
y = (y * zi) % q
z = 1
t = (x * y) % q
return (x, y, z, t)

@ -53,6 +53,9 @@ class Offer(Base):
expire_at = sa.Column(sa.BigInteger) expire_at = sa.Column(sa.BigInteger)
was_sent = sa.Column(sa.Boolean) was_sent = sa.Column(sa.Boolean)
from_feerate = sa.Column(sa.BigInteger)
to_feerate = sa.Column(sa.BigInteger)
auto_accept_bids = sa.Column(sa.Boolean) auto_accept_bids = sa.Column(sa.Boolean)
state = sa.Column(sa.Integer) state = sa.Column(sa.Integer)
@ -125,10 +128,13 @@ class Bid(Base):
self.participate_tx.state = new_state self.participate_tx.state = new_state
self.participate_tx.states = (self.participate_tx.states if self.participate_tx.states is not None else bytes()) + struct.pack('<iq', new_state, int(time.time())) self.participate_tx.states = (self.participate_tx.states if self.participate_tx.states is not None else bytes()) + struct.pack('<iq', new_state, int(time.time()))
def setState(self, new_state): def setState(self, new_state, state_note=None):
now = int(time.time()) now = int(time.time())
self.state = new_state self.state = new_state
self.state_time = now self.state_time = now
if state_note is not None:
self.state_note = state_note
if self.states is None: if self.states is None:
self.states = struct.pack('<iq', new_state, now) self.states = struct.pack('<iq', new_state, now)
else: else:
@ -195,3 +201,75 @@ class EventQueue(Base):
linked_id = sa.Column(sa.LargeBinary) linked_id = sa.Column(sa.LargeBinary)
event_type = sa.Column(sa.Integer) event_type = sa.Column(sa.Integer)
event_data = sa.Column(sa.LargeBinary) event_data = sa.Column(sa.LargeBinary)
class XmrOffer(Base):
__tablename__ = 'xmr_offers'
swap_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
offer_id = sa.Column(sa.LargeBinary, sa.ForeignKey('offers.offer_id'))
a_fee_rate = sa.Column(sa.BigInteger)
b_fee_rate = sa.Column(sa.BigInteger)
lock_time_1 = sa.Column(sa.Integer) # Delay before the chain a lock refund tx can be mined
lock_time_2 = sa.Column(sa.Integer) # Delay before the follower can spend from the chain a lock refund tx
class XmrSwap(Base):
__tablename__ = 'xmr_swaps'
swap_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
bid_id = sa.Column(sa.LargeBinary, sa.ForeignKey('bids.bid_id'))
bid_msg_id2 = sa.Column(sa.LargeBinary)
bid_msg_id3 = sa.Column(sa.LargeBinary)
bid_accept_msg_id = sa.Column(sa.LargeBinary)
bid_accept_msg_id2 = sa.Column(sa.LargeBinary)
bid_accept_msg_id3 = sa.Column(sa.LargeBinary)
contract_count = sa.Column(sa.Integer)
sh = sa.Column(sa.LargeBinary) # Secret hash
pkal = sa.Column(sa.LargeBinary)
pkarl = sa.Column(sa.LargeBinary)
pkaf = sa.Column(sa.LargeBinary)
pkarf = sa.Column(sa.LargeBinary)
vkbvl = sa.Column(sa.LargeBinary)
vkbsl = sa.Column(sa.LargeBinary)
pkbvl = sa.Column(sa.LargeBinary)
pkbsl = sa.Column(sa.LargeBinary)
vkbvf = sa.Column(sa.LargeBinary)
vkbsf = sa.Column(sa.LargeBinary)
pkbvf = sa.Column(sa.LargeBinary)
pkbsf = sa.Column(sa.LargeBinary)
kbsl_dleag = sa.Column(sa.LargeBinary)
kbsf_dleag = sa.Column(sa.LargeBinary)
a_lock_tx = sa.Column(sa.LargeBinary)
a_lock_tx_script = sa.Column(sa.LargeBinary)
a_lock_refund_tx = sa.Column(sa.LargeBinary)
a_lock_refund_tx_script = sa.Column(sa.LargeBinary)
a_swap_refund_value = sa.Column(sa.BigInteger)
a_lock_refund_spend_tx = sa.Column(sa.LargeBinary)
b_restore_height = sa.Column(sa.Integer) # Height of xmr chain before the swap
class XmrSplitData(Base):
__tablename__ = 'xmr_split_data'
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
bid_id = sa.Column(sa.LargeBinary)
msg_type = sa.Column(sa.Integer)
msg_sequence = sa.Column(sa.Integer)
dleag = sa.Column(sa.LargeBinary)
created_at = sa.Column(sa.BigInteger)

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
import secrets
import hashlib
import basicswap.contrib.ed25519_fast as edf
def get_secret():
return 9 + secrets.randbelow(edf.l - 9)
def encodepoint(P):
zi = edf.inv(P[2])
x = (P[0] * zi) % edf.q
y = (P[1] * zi) % edf.q
y += ((x & 1) << 255)
return y.to_bytes(32, byteorder='little')
def hashToEd25519(bytes_in):
hashed = hashlib.sha256(bytes_in).digest()
for i in range(1000):
h255 = bytearray(hashed)
x_sign = 0 if h255[31] & 0x80 == 0 else 1
h255[31] &= 0x7f # Clear top bit
y = int.from_bytes(h255, byteorder='little')
x = edf.xrecover(y, x_sign)
if x == 0 and y == 1: # Skip infinity point
continue
P = [x, y, 1, (x * y) % edf.q]
# Keep trying until the point is in the correct subgroup
if edf.isoncurve(P) and edf.is_identity(edf.scalarmult(P, edf.l)):
return P
hashed = hashlib.sha256(hashed).digest()
raise ValueError('hashToEd25519 failed')

@ -17,6 +17,10 @@ from .util import (
format_amount, format_amount,
make_int make_int
) )
from coincurve.keys import (
PublicKey)
from coincurve.dleag import (
verify_secp256k1_point)
from .ecc_util import ( from .ecc_util import (
G, ep, G, ep,
@ -126,6 +130,16 @@ class BTCInterface(CoinInterface):
def pubkey(self, key): def pubkey(self, key):
return G * key return G * key
def getPubkey(self, privkey):
return PublicKey.from_secret(privkey).format()
def verifyKey(self, k):
i = b2i(k)
return(i < ep.o and i > 0)
def verifyPubkey(self, pubkey_bytes):
return verify_secp256k1_point(pubkey_bytes)
def encodePubkey(self, pk): def encodePubkey(self, pk):
return pointToCPK(pk) return pointToCPK(pk)
@ -192,14 +206,20 @@ class BTCInterface(CoinInterface):
return secret_hash, pk1, pk2, csv_val, pk3, pk4 return secret_hash, pk1, pk2, csv_val, pk3, pk4
def genScriptLockTxScript(self, sh, Kal, Kaf, lock_blocks, Karl, Karf): def genScriptLockTxScript(self, sh, Kal, Kaf, lock_blocks, Karl, Karf):
Kal_enc = Kal if len(Kal) == 33 else self.encodePubkey(Kal)
Kaf_enc = Kaf if len(Kaf) == 33 else self.encodePubkey(Kaf)
Karl_enc = Karl if len(Karl) == 33 else self.encodePubkey(Karl)
Karf_enc = Karf if len(Karf) == 33 else self.encodePubkey(Karf)
return CScript([ return CScript([
CScriptOp(OP_IF), CScriptOp(OP_IF),
CScriptOp(OP_SIZE), 32, CScriptOp(OP_EQUALVERIFY), CScriptOp(OP_SIZE), 32, CScriptOp(OP_EQUALVERIFY),
CScriptOp(OP_SHA256), sh, CScriptOp(OP_EQUALVERIFY), CScriptOp(OP_SHA256), sh, CScriptOp(OP_EQUALVERIFY),
2, self.encodePubkey(Kal), self.encodePubkey(Kaf), 2, CScriptOp(OP_CHECKMULTISIG), 2, Kal_enc, Kaf_enc, 2, CScriptOp(OP_CHECKMULTISIG),
CScriptOp(OP_ELSE), CScriptOp(OP_ELSE),
lock_blocks, CScriptOp(OP_CHECKSEQUENCEVERIFY), CScriptOp(OP_DROP), lock_blocks, CScriptOp(OP_CHECKSEQUENCEVERIFY), CScriptOp(OP_DROP),
2, self.encodePubkey(Karl), self.encodePubkey(Karf), 2, CScriptOp(OP_CHECKMULTISIG), 2, Karl_enc, Karf_enc, 2, CScriptOp(OP_CHECKMULTISIG),
CScriptOp(OP_ENDIF)]) CScriptOp(OP_ENDIF)])
def createScriptLockTx(self, value, sh, Kal, Kaf, lock_blocks, Karl, Karf): def createScriptLockTx(self, value, sh, Kal, Kaf, lock_blocks, Karl, Karf):
@ -209,7 +229,7 @@ class BTCInterface(CoinInterface):
tx.nVersion = self.txVersion() tx.nVersion = self.txVersion()
tx.vout.append(self.txoType(value, CScript([OP_0, hashlib.sha256(script).digest()]))) tx.vout.append(self.txoType(value, CScript([OP_0, hashlib.sha256(script).digest()])))
return tx, script return tx.serialize(), script
def extractScriptLockRefundScriptValues(self, script_bytes): def extractScriptLockRefundScriptValues(self, script_bytes):
script_len = len(script_bytes) script_len = len(script_bytes)
@ -243,15 +263,22 @@ class BTCInterface(CoinInterface):
return pk1, pk2, csv_val, pk3 return pk1, pk2, csv_val, pk3
def genScriptLockRefundTxScript(self, Karl, Karf, csv_val, Kaf): def genScriptLockRefundTxScript(self, Karl, Karf, csv_val, Kaf):
Kaf_enc = Kaf if len(Kaf) == 33 else self.encodePubkey(Kaf)
Karl_enc = Karl if len(Karl) == 33 else self.encodePubkey(Karl)
Karf_enc = Karf if len(Karf) == 33 else self.encodePubkey(Karf)
return CScript([ return CScript([
CScriptOp(OP_IF), CScriptOp(OP_IF),
2, self.encodePubkey(Karl), self.encodePubkey(Karf), 2, CScriptOp(OP_CHECKMULTISIG), 2, Karl_enc, Karf_enc, 2, CScriptOp(OP_CHECKMULTISIG),
CScriptOp(OP_ELSE), CScriptOp(OP_ELSE),
csv_val, CScriptOp(OP_CHECKSEQUENCEVERIFY), CScriptOp(OP_DROP), csv_val, CScriptOp(OP_CHECKSEQUENCEVERIFY), CScriptOp(OP_DROP),
self.encodePubkey(Kaf), CScriptOp(OP_CHECKSIG), Kaf_enc, CScriptOp(OP_CHECKSIG),
CScriptOp(OP_ENDIF)]) CScriptOp(OP_ENDIF)])
def createScriptLockRefundTx(self, tx_lock, script_lock, Karl, Karf, csv_val, Kaf, tx_fee_rate): def createScriptLockRefundTx(self, tx_lock_bytes, script_lock, Karl, Karf, csv_val, Kaf, tx_fee_rate):
tx_lock = CTransaction()
tx_lock = FromHex(tx_lock, tx_lock_bytes.hex())
output_script = CScript([OP_0, hashlib.sha256(script_lock).digest()]) output_script = CScript([OP_0, hashlib.sha256(script_lock).digest()])
locked_n = findOutput(tx_lock, output_script) locked_n = findOutput(tx_lock, output_script)
@ -281,13 +308,15 @@ class BTCInterface(CoinInterface):
logging.info('createScriptLockRefundTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', logging.info('createScriptLockRefundTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
return tx, refund_script, tx.vout[0].nValue return tx.serialize(), refund_script, tx.vout[0].nValue
def createScriptLockRefundSpendTx(self, tx_lock_refund, script_lock_refund, Kal, tx_fee_rate): def createScriptLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, Kal, tx_fee_rate):
# Returns the coinA locked coin to the leader # 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 # The follower will sign the multisig path with a signature encumbered by the leader's coinB spend pubkey
# When the leader publishes the decrypted signature the leader's coinB spend privatekey will be revealed to the follower # When 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 = CScript([OP_0, hashlib.sha256(script_lock_refund).digest()])
locked_n = findOutput(tx_lock_refund, output_script) locked_n = findOutput(tx_lock_refund, output_script)
assert_cond(locked_n is not None, 'Output not found in tx') assert_cond(locked_n is not None, 'Output not found in tx')
@ -300,7 +329,8 @@ class BTCInterface(CoinInterface):
tx.nVersion = self.txVersion() 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))
pubkeyhash = hash160(self.encodePubkey(Kal)) #pubkeyhash = hash160(self.encodePubkey(Kal))
pubkeyhash = hash160(Kal)
tx.vout.append(self.txoType(locked_coin, CScript([OP_0, pubkeyhash]))) tx.vout.append(self.txoType(locked_coin, CScript([OP_0, pubkeyhash])))
witness_bytes = len(script_lock_refund) witness_bytes = len(script_lock_refund)
@ -315,7 +345,7 @@ class BTCInterface(CoinInterface):
logging.info('createScriptLockRefundSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', logging.info('createScriptLockRefundSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
return tx return tx.serialize()
def createScriptLockRefundSpendToFTx(self, tx_lock_refund, script_lock_refund, pkh_dest, tx_fee_rate): def createScriptLockRefundSpendToFTx(self, tx_lock_refund, script_lock_refund, pkh_dest, tx_fee_rate):
# Sends the coinA locked coin to the follower # Sends the coinA locked coin to the follower
@ -347,7 +377,7 @@ class BTCInterface(CoinInterface):
logging.info('createScriptLockRefundSpendToFTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', logging.info('createScriptLockRefundSpendToFTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
return tx return tx.serialize()
def createScriptLockSpendTx(self, tx_lock, script_lock, pkh_dest, tx_fee_rate): def createScriptLockSpendTx(self, tx_lock, script_lock, pkh_dest, tx_fee_rate):
@ -379,7 +409,7 @@ class BTCInterface(CoinInterface):
logging.info('createScriptLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', logging.info('createScriptLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
return tx return tx.serialize()
def verifyLockTx(self, tx, script_out, def verifyLockTx(self, tx, script_out,
swap_value, swap_value,
@ -592,11 +622,13 @@ class BTCInterface(CoinInterface):
return True return True
def signTx(self, key_int, tx, prevout_n, prevout_script, prevout_value): def signTx(self, key_bytes, tx_bytes, prevout_n, prevout_script, prevout_value):
# TODO: use libsecp356k1
tx = self.loadTx(tx_bytes)
sig_hash = SegwitV0SignatureHash(prevout_script, tx, prevout_n, SIGHASH_ALL, prevout_value) sig_hash = SegwitV0SignatureHash(prevout_script, tx, prevout_n, SIGHASH_ALL, prevout_value)
eck = ECKey() eck = ECKey()
eck.set(i2b(key_int), compressed=True) eck.set(key_bytes, compressed=True)
return eck.sign_ecdsa(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL return eck.sign_ecdsa(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL
@ -611,22 +643,25 @@ class BTCInterface(CoinInterface):
def decryptOtVES(self, k, esig): def decryptOtVES(self, k, esig):
return otves.DecSig(k, esig) + b'\x01' # 0x1 is SIGHASH_ALL return otves.DecSig(k, esig) + b'\x01' # 0x1 is SIGHASH_ALL
def verifyTxSig(self, tx, sig, K, prevout_n, prevout_script, prevout_value): def verifyTxSig(self, tx_bytes, sig, K, prevout_n, prevout_script, prevout_value):
tx = self.loadTx(tx_bytes)
sig_hash = SegwitV0SignatureHash(prevout_script, tx, prevout_n, SIGHASH_ALL, prevout_value) sig_hash = SegwitV0SignatureHash(prevout_script, tx, prevout_n, SIGHASH_ALL, prevout_value)
ecK = ECPubKey() ecK = ECPubKey()
ecK.set_int(K.x(), K.y()) #ecK.set_int(K.x(), K.y())
ecK.set(K)
return ecK.verify_ecdsa(sig[: -1], sig_hash) # Pop the hashtype byte return ecK.verify_ecdsa(sig[: -1], sig_hash) # Pop the hashtype byte
def fundTx(self, tx, feerate): def fundTx(self, tx, feerate):
feerate_str = format_amount(feerate, self.exp()) feerate_str = format_amount(feerate, self.exp())
rv = self.rpc_callback('fundrawtransaction', [ToHex(tx), {'feeRate': feerate_str}]) rv = self.rpc_callback('fundrawtransaction', [tx.hex(), {'feeRate': feerate_str}])
return FromHex(tx, rv['hex']) return bytes.fromhex(rv['hex'])
def signTxWithWallet(self, tx): def signTxWithWallet(self, tx):
rv = self.rpc_callback('signrawtransactionwithwallet', [ToHex(tx)]) rv = self.rpc_callback('signrawtransactionwithwallet', [ToHex(tx)])
return FromHex(tx, rv['hex']) #return FromHex(tx, rv['hex'])
return bytes.fromhex(rv['hex'])
def publishTx(self, tx): def publishTx(self, tx):
return self.rpc_callback('sendrawtransaction', [ToHex(tx)]) return self.rpc_callback('sendrawtransaction', [ToHex(tx)])
@ -640,7 +675,9 @@ class BTCInterface(CoinInterface):
tx.deserialize(BytesIO(tx_bytes)) tx.deserialize(BytesIO(tx_bytes))
return tx return tx
def getTxHash(self, tx): def getTxHash(self, tx_bytes):
tx = CTransaction()
tx = FromHex(tx, tx_bytes.hex())
tx.rehash() tx.rehash()
return i2b(tx.sha256) return i2b(tx.sha256)
@ -680,7 +717,7 @@ class BTCInterface(CoinInterface):
tx.nVersion = self.txVersion() tx.nVersion = self.txVersion()
p2wpkh = self.getPkDest(Kbs) p2wpkh = self.getPkDest(Kbs)
tx.vout.append(self.txoType(output_amount, p2wpkh)) tx.vout.append(self.txoType(output_amount, p2wpkh))
return tx return tx.serialize()
def publishBLockTx(self, Kbv, Kbs, output_amount, feerate): def publishBLockTx(self, Kbv, Kbs, output_amount, feerate):
b_lock_tx = self.createBLockTx(Kbs, output_amount) b_lock_tx = self.createBLockTx(Kbs, output_amount)

@ -8,11 +8,24 @@
import time import time
import logging import logging
from .chainparams import CoinInterface import basicswap.contrib.ed25519_fast as edf
from .rpc_xmr import make_xmr_rpc_func, make_xmr_wallet_rpc_func import basicswap.ed25519_fast_util as edu
from coincurve.ed25519 import ed25519_get_pubkey
from coincurve.keys import PrivateKey
from coincurve.dleag import (
verify_ed25519_point,
dleag_proof_len,
dleag_verify,
dleag_prove)
from .util import ( from .util import (
format_amount format_amount)
) from .rpc_xmr import (
make_xmr_rpc_func,
make_xmr_wallet_rpc_func)
from .ecc_util import (
b2i)
from .chainparams import CoinInterface
XMR_COIN = 10 ** 12 XMR_COIN = 10 ** 12
@ -49,6 +62,9 @@ class XMRInterface(CoinInterface):
rv['verificationprogress'] = 0 # TODO rv['verificationprogress'] = 0 # TODO
return rv return rv
def getChainHeight(self):
return self.rpc_cb('get_block_count')['count']
def getWalletInfo(self): def getWalletInfo(self):
rv = {} rv = {}
balance_info = self.rpc_wallet_cb('get_balance') balance_info = self.rpc_wallet_cb('get_balance')
@ -60,6 +76,10 @@ class XMRInterface(CoinInterface):
logging.debug('TODO - subaddress?') logging.debug('TODO - subaddress?')
return self.rpc_wallet_cb('get_address')['address'] return self.rpc_wallet_cb('get_address')['address']
def isValidKey(self, key_bytes):
ki = b2i(key_bytes)
return ki < edf.l and ki > 8
def getNewSecretKey(self): def getNewSecretKey(self):
return edu.get_secret() return edu.get_secret()
@ -72,6 +92,26 @@ class XMRInterface(CoinInterface):
def decodePubkey(self, pke): def decodePubkey(self, pke):
return edf.decodepoint(pke) return edf.decodepoint(pke)
def getPubkey(self, privkey):
return ed25519_get_pubkey(privkey)
def verifyKey(self, k):
i = b2i(k)
return(i < edf.l and i > 8)
def verifyPubkey(self, pubkey_bytes):
return verify_ed25519_point(pubkey_bytes)
def proveDLEAG(self, key):
privkey = PrivateKey(key)
return dleag_prove(privkey)
def verifyDLEAG(self, dleag_bytes):
return dleag_verify(dleag_bytes)
def lengthDLEAG(self):
return dleag_proof_len()
def decodeKey(self, k): def decodeKey(self, k):
i = b2i(k) i = b2i(k)
assert(i < edf.l and i > 8) assert(i < edf.l and i > 8)

@ -26,6 +26,9 @@ message OfferMessage {
string proof_signature = 11; string proof_signature = 11;
bytes pkhash_seller = 12; bytes pkhash_seller = 12;
bytes secret_hash = 13; bytes secret_hash = 13;
uint64 fee_rate_from = 14;
uint64 fee_rate_to = 15;
} }
/* Step 2, buyer -> seller */ /* Step 2, buyer -> seller */
@ -48,29 +51,44 @@ message BidAcceptMessage {
} }
message XmrOfferMessage {
uint32 coin_from = 1;
uint32 coin_to = 2;
uint64 amount_from = 3;
uint64 rate = 4;
uint64 min_bid_amount = 5;
uint64 time_valid = 6;
enum LockType {
NOT_SET = 0;
SEQUENCE_LOCK_BLOCKS = 1;
SEQUENCE_LOCK_TIME = 2;
ABS_LOCK_BLOCKS = 3;
ABS_LOCK_TIME = 4;
}
LockType lock_type = 7;
uint32 lock_value = 8;
uint32 swap_type = 9;
/* Step 2, buyer -> seller */
message XmrBidMessage {
bytes offer_msg_id = 1;
uint64 time_valid = 2; /* seconds bid is valid for */
uint64 amount = 3; /* amount of amount_from bid is for */
bytes pkaf = 4;
bytes pkarf = 5;
bytes kbvf = 6;
bytes kbsf_dleag = 7;
}
/* optional */ message XmrSplitMessage {
string proof_address = 10; bytes msg_id = 1;
string proof_signature = 11; uint32 msg_type = 2; /* 1 XmrBid, 2 XmrBidAccept */
bytes pkhash_seller = 12; uint32 sequence = 3;
bytes secret_hash = 13; bytes dleag = 4;
} }
message XmrBidAcceptMessage {
bytes bid_msg_id = 1;
bytes sh = 2;
bytes pkal = 3;
bytes pkarl = 4;
bytes kbvl = 5;
bytes kbsl_dleag = 6;
/* MSG2F */
bytes a_lock_tx = 7;
bytes a_lock_tx_script = 8;
bytes a_lock_refund_tx = 9;
bytes a_lock_refund_tx_script = 10;
bytes a_lock_refund_spend_tx = 11;
bytes al_lock_refund_tx_sig = 12;
}

@ -19,7 +19,7 @@ DESCRIPTOR = _descriptor.FileDescriptor(
syntax='proto3', syntax='proto3',
serialized_options=None, serialized_options=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
serialized_pb=b'\n\x0emessages.proto\x12\tbasicswap\"\xac\x03\n\x0cOfferMessage\x12\x11\n\tcoin_from\x18\x01 \x01(\r\x12\x0f\n\x07\x63oin_to\x18\x02 \x01(\r\x12\x13\n\x0b\x61mount_from\x18\x03 \x01(\x04\x12\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x16\n\x0emin_bid_amount\x18\x05 \x01(\x04\x12\x12\n\ntime_valid\x18\x06 \x01(\x04\x12\x33\n\tlock_type\x18\x07 \x01(\x0e\x32 .basicswap.OfferMessage.LockType\x12\x12\n\nlock_value\x18\x08 \x01(\r\x12\x11\n\tswap_type\x18\t \x01(\r\x12\x15\n\rproof_address\x18\n \x01(\t\x12\x17\n\x0fproof_signature\x18\x0b \x01(\t\x12\x15\n\rpkhash_seller\x18\x0c \x01(\x0c\x12\x13\n\x0bsecret_hash\x18\r \x01(\x0c\"q\n\x08LockType\x12\x0b\n\x07NOT_SET\x10\x00\x12\x18\n\x14SEQUENCE_LOCK_BLOCKS\x10\x01\x12\x16\n\x12SEQUENCE_LOCK_TIME\x10\x02\x12\x13\n\x0f\x41\x42S_LOCK_BLOCKS\x10\x03\x12\x11\n\rABS_LOCK_TIME\x10\x04\"\x8c\x01\n\nBidMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x04\x12\x14\n\x0cpkhash_buyer\x18\x04 \x01(\x0c\x12\x15\n\rproof_address\x18\x05 \x01(\t\x12\x17\n\x0fproof_signature\x18\x06 \x01(\t\"V\n\x10\x42idAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x15\n\rinitiate_txid\x18\x02 \x01(\x0c\x12\x17\n\x0f\x63ontract_script\x18\x03 \x01(\x0c\"\xb2\x03\n\x0fXmrOfferMessage\x12\x11\n\tcoin_from\x18\x01 \x01(\r\x12\x0f\n\x07\x63oin_to\x18\x02 \x01(\r\x12\x13\n\x0b\x61mount_from\x18\x03 \x01(\x04\x12\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x16\n\x0emin_bid_amount\x18\x05 \x01(\x04\x12\x12\n\ntime_valid\x18\x06 \x01(\x04\x12\x36\n\tlock_type\x18\x07 \x01(\x0e\x32#.basicswap.XmrOfferMessage.LockType\x12\x12\n\nlock_value\x18\x08 \x01(\r\x12\x11\n\tswap_type\x18\t \x01(\r\x12\x15\n\rproof_address\x18\n \x01(\t\x12\x17\n\x0fproof_signature\x18\x0b \x01(\t\x12\x15\n\rpkhash_seller\x18\x0c \x01(\x0c\x12\x13\n\x0bsecret_hash\x18\r \x01(\x0c\"q\n\x08LockType\x12\x0b\n\x07NOT_SET\x10\x00\x12\x18\n\x14SEQUENCE_LOCK_BLOCKS\x10\x01\x12\x16\n\x12SEQUENCE_LOCK_TIME\x10\x02\x12\x13\n\x0f\x41\x42S_LOCK_BLOCKS\x10\x03\x12\x11\n\rABS_LOCK_TIME\x10\x04\x62\x06proto3' serialized_pb=b'\n\x0emessages.proto\x12\tbasicswap\"\xd8\x03\n\x0cOfferMessage\x12\x11\n\tcoin_from\x18\x01 \x01(\r\x12\x0f\n\x07\x63oin_to\x18\x02 \x01(\r\x12\x13\n\x0b\x61mount_from\x18\x03 \x01(\x04\x12\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x16\n\x0emin_bid_amount\x18\x05 \x01(\x04\x12\x12\n\ntime_valid\x18\x06 \x01(\x04\x12\x33\n\tlock_type\x18\x07 \x01(\x0e\x32 .basicswap.OfferMessage.LockType\x12\x12\n\nlock_value\x18\x08 \x01(\r\x12\x11\n\tswap_type\x18\t \x01(\r\x12\x15\n\rproof_address\x18\n \x01(\t\x12\x17\n\x0fproof_signature\x18\x0b \x01(\t\x12\x15\n\rpkhash_seller\x18\x0c \x01(\x0c\x12\x13\n\x0bsecret_hash\x18\r \x01(\x0c\x12\x15\n\rfee_rate_from\x18\x0e \x01(\x04\x12\x13\n\x0b\x66\x65\x65_rate_to\x18\x0f \x01(\x04\"q\n\x08LockType\x12\x0b\n\x07NOT_SET\x10\x00\x12\x18\n\x14SEQUENCE_LOCK_BLOCKS\x10\x01\x12\x16\n\x12SEQUENCE_LOCK_TIME\x10\x02\x12\x13\n\x0f\x41\x42S_LOCK_BLOCKS\x10\x03\x12\x11\n\rABS_LOCK_TIME\x10\x04\"\x8c\x01\n\nBidMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x04\x12\x14\n\x0cpkhash_buyer\x18\x04 \x01(\x0c\x12\x15\n\rproof_address\x18\x05 \x01(\t\x12\x17\n\x0fproof_signature\x18\x06 \x01(\t\"V\n\x10\x42idAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x15\n\rinitiate_txid\x18\x02 \x01(\x0c\x12\x17\n\x0f\x63ontract_script\x18\x03 \x01(\x0c\"\x88\x01\n\rXmrBidMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x04\x12\x0c\n\x04pkaf\x18\x04 \x01(\x0c\x12\r\n\x05pkarf\x18\x05 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x06 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x07 \x01(\x0c\"T\n\x0fXmrSplitMessage\x12\x0e\n\x06msg_id\x18\x01 \x01(\x0c\x12\x10\n\x08msg_type\x18\x02 \x01(\r\x12\x10\n\x08sequence\x18\x03 \x01(\r\x12\r\n\x05\x64leag\x18\x04 \x01(\x0c\"\x9b\x02\n\x13XmrBidAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\n\n\x02sh\x18\x02 \x01(\x0c\x12\x0c\n\x04pkal\x18\x03 \x01(\x0c\x12\r\n\x05pkarl\x18\x04 \x01(\x0c\x12\x0c\n\x04kbvl\x18\x05 \x01(\x0c\x12\x12\n\nkbsl_dleag\x18\x06 \x01(\x0c\x12\x11\n\ta_lock_tx\x18\x07 \x01(\x0c\x12\x18\n\x10\x61_lock_tx_script\x18\x08 \x01(\x0c\x12\x18\n\x10\x61_lock_refund_tx\x18\t \x01(\x0c\x12\x1f\n\x17\x61_lock_refund_tx_script\x18\n \x01(\x0c\x12\x1e\n\x16\x61_lock_refund_spend_tx\x18\x0b \x01(\x0c\x12\x1d\n\x15\x61l_lock_refund_tx_sig\x18\x0c \x01(\x0c\x62\x06proto3'
) )
@ -59,51 +59,11 @@ _OFFERMESSAGE_LOCKTYPE = _descriptor.EnumDescriptor(
], ],
containing_type=None, containing_type=None,
serialized_options=None, serialized_options=None,
serialized_start=345, serialized_start=389,
serialized_end=458, serialized_end=502,
) )
_sym_db.RegisterEnumDescriptor(_OFFERMESSAGE_LOCKTYPE) _sym_db.RegisterEnumDescriptor(_OFFERMESSAGE_LOCKTYPE)
_XMROFFERMESSAGE_LOCKTYPE = _descriptor.EnumDescriptor(
name='LockType',
full_name='basicswap.XmrOfferMessage.LockType',
filename=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key,
values=[
_descriptor.EnumValueDescriptor(
name='NOT_SET', index=0, number=0,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
_descriptor.EnumValueDescriptor(
name='SEQUENCE_LOCK_BLOCKS', index=1, number=1,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
_descriptor.EnumValueDescriptor(
name='SEQUENCE_LOCK_TIME', index=2, number=2,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
_descriptor.EnumValueDescriptor(
name='ABS_LOCK_BLOCKS', index=3, number=3,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
_descriptor.EnumValueDescriptor(
name='ABS_LOCK_TIME', index=4, number=4,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
],
containing_type=None,
serialized_options=None,
serialized_start=345,
serialized_end=458,
)
_sym_db.RegisterEnumDescriptor(_XMROFFERMESSAGE_LOCKTYPE)
_OFFERMESSAGE = _descriptor.Descriptor( _OFFERMESSAGE = _descriptor.Descriptor(
name='OfferMessage', name='OfferMessage',
@ -204,6 +164,20 @@ _OFFERMESSAGE = _descriptor.Descriptor(
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='fee_rate_from', full_name='basicswap.OfferMessage.fee_rate_from', index=13,
number=14, type=4, cpp_type=4, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='fee_rate_to', full_name='basicswap.OfferMessage.fee_rate_to', index=14,
number=15, type=4, cpp_type=4, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
], ],
extensions=[ extensions=[
], ],
@ -218,7 +192,7 @@ _OFFERMESSAGE = _descriptor.Descriptor(
oneofs=[ oneofs=[
], ],
serialized_start=30, serialized_start=30,
serialized_end=458, serialized_end=502,
) )
@ -284,8 +258,8 @@ _BIDMESSAGE = _descriptor.Descriptor(
extension_ranges=[], extension_ranges=[],
oneofs=[ oneofs=[
], ],
serialized_start=461, serialized_start=505,
serialized_end=601, serialized_end=645,
) )
@ -330,106 +304,226 @@ _BIDACCEPTMESSAGE = _descriptor.Descriptor(
extension_ranges=[], extension_ranges=[],
oneofs=[ oneofs=[
], ],
serialized_start=603, serialized_start=647,
serialized_end=689, serialized_end=733,
) )
_XMROFFERMESSAGE = _descriptor.Descriptor( _XMRBIDMESSAGE = _descriptor.Descriptor(
name='XmrOfferMessage', name='XmrBidMessage',
full_name='basicswap.XmrOfferMessage', full_name='basicswap.XmrBidMessage',
filename=None, filename=None,
file=DESCRIPTOR, file=DESCRIPTOR,
containing_type=None, containing_type=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
fields=[ fields=[
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='coin_from', full_name='basicswap.XmrOfferMessage.coin_from', index=0, name='offer_msg_id', full_name='basicswap.XmrBidMessage.offer_msg_id', index=0,
number=1, type=13, cpp_type=3, label=1, number=1, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='coin_to', full_name='basicswap.XmrOfferMessage.coin_to', index=1, name='time_valid', full_name='basicswap.XmrBidMessage.time_valid', index=1,
number=2, type=13, cpp_type=3, label=1, number=2, type=4, cpp_type=4, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='amount_from', full_name='basicswap.XmrOfferMessage.amount_from', index=2, name='amount', full_name='basicswap.XmrBidMessage.amount', index=2,
number=3, type=4, cpp_type=4, label=1, number=3, type=4, cpp_type=4, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='rate', full_name='basicswap.XmrOfferMessage.rate', index=3, name='pkaf', full_name='basicswap.XmrBidMessage.pkaf', index=3,
number=4, type=4, cpp_type=4, label=1, number=4, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='min_bid_amount', full_name='basicswap.XmrOfferMessage.min_bid_amount', index=4, name='pkarf', full_name='basicswap.XmrBidMessage.pkarf', index=4,
number=5, type=4, cpp_type=4, label=1, number=5, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='time_valid', full_name='basicswap.XmrOfferMessage.time_valid', index=5, name='kbvf', full_name='basicswap.XmrBidMessage.kbvf', index=5,
number=6, type=4, cpp_type=4, label=1, number=6, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='lock_type', full_name='basicswap.XmrOfferMessage.lock_type', index=6, name='kbsf_dleag', full_name='basicswap.XmrBidMessage.kbsf_dleag', index=6,
number=7, type=14, cpp_type=8, label=1, number=7, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=736,
serialized_end=872,
)
_XMRSPLITMESSAGE = _descriptor.Descriptor(
name='XmrSplitMessage',
full_name='basicswap.XmrSplitMessage',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='lock_value', full_name='basicswap.XmrOfferMessage.lock_value', index=7, name='msg_id', full_name='basicswap.XmrSplitMessage.msg_id', index=0,
number=8, type=13, cpp_type=3, label=1, number=1, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='msg_type', full_name='basicswap.XmrSplitMessage.msg_type', index=1,
number=2, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='swap_type', full_name='basicswap.XmrOfferMessage.swap_type', index=8, name='sequence', full_name='basicswap.XmrSplitMessage.sequence', index=2,
number=9, type=13, cpp_type=3, label=1, number=3, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='proof_address', full_name='basicswap.XmrOfferMessage.proof_address', index=9, name='dleag', full_name='basicswap.XmrSplitMessage.dleag', index=3,
number=10, type=9, cpp_type=9, label=1, number=4, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'), has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=874,
serialized_end=958,
)
_XMRBIDACCEPTMESSAGE = _descriptor.Descriptor(
name='XmrBidAcceptMessage',
full_name='basicswap.XmrBidAcceptMessage',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='proof_signature', full_name='basicswap.XmrOfferMessage.proof_signature', index=10, name='bid_msg_id', full_name='basicswap.XmrBidAcceptMessage.bid_msg_id', index=0,
number=11, type=9, cpp_type=9, label=1, number=1, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'), has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='pkhash_seller', full_name='basicswap.XmrOfferMessage.pkhash_seller', index=11, name='sh', full_name='basicswap.XmrBidAcceptMessage.sh', index=1,
number=12, type=12, cpp_type=9, label=1, number=2, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"", has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='secret_hash', full_name='basicswap.XmrOfferMessage.secret_hash', index=12, name='pkal', full_name='basicswap.XmrBidAcceptMessage.pkal', index=2,
number=13, type=12, cpp_type=9, label=1, number=3, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='pkarl', full_name='basicswap.XmrBidAcceptMessage.pkarl', index=3,
number=4, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='kbvl', full_name='basicswap.XmrBidAcceptMessage.kbvl', index=4,
number=5, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='kbsl_dleag', full_name='basicswap.XmrBidAcceptMessage.kbsl_dleag', index=5,
number=6, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='a_lock_tx', full_name='basicswap.XmrBidAcceptMessage.a_lock_tx', index=6,
number=7, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='a_lock_tx_script', full_name='basicswap.XmrBidAcceptMessage.a_lock_tx_script', index=7,
number=8, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='a_lock_refund_tx', full_name='basicswap.XmrBidAcceptMessage.a_lock_refund_tx', index=8,
number=9, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='a_lock_refund_tx_script', full_name='basicswap.XmrBidAcceptMessage.a_lock_refund_tx_script', index=9,
number=10, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='a_lock_refund_spend_tx', full_name='basicswap.XmrBidAcceptMessage.a_lock_refund_spend_tx', index=10,
number=11, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='al_lock_refund_tx_sig', full_name='basicswap.XmrBidAcceptMessage.al_lock_refund_tx_sig', index=11,
number=12, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"", has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
@ -439,7 +533,6 @@ _XMROFFERMESSAGE = _descriptor.Descriptor(
], ],
nested_types=[], nested_types=[],
enum_types=[ enum_types=[
_XMROFFERMESSAGE_LOCKTYPE,
], ],
serialized_options=None, serialized_options=None,
is_extendable=False, is_extendable=False,
@ -447,18 +540,18 @@ _XMROFFERMESSAGE = _descriptor.Descriptor(
extension_ranges=[], extension_ranges=[],
oneofs=[ oneofs=[
], ],
serialized_start=692, serialized_start=961,
serialized_end=1126, serialized_end=1244,
) )
_OFFERMESSAGE.fields_by_name['lock_type'].enum_type = _OFFERMESSAGE_LOCKTYPE _OFFERMESSAGE.fields_by_name['lock_type'].enum_type = _OFFERMESSAGE_LOCKTYPE
_OFFERMESSAGE_LOCKTYPE.containing_type = _OFFERMESSAGE _OFFERMESSAGE_LOCKTYPE.containing_type = _OFFERMESSAGE
_XMROFFERMESSAGE.fields_by_name['lock_type'].enum_type = _XMROFFERMESSAGE_LOCKTYPE
_XMROFFERMESSAGE_LOCKTYPE.containing_type = _XMROFFERMESSAGE
DESCRIPTOR.message_types_by_name['OfferMessage'] = _OFFERMESSAGE DESCRIPTOR.message_types_by_name['OfferMessage'] = _OFFERMESSAGE
DESCRIPTOR.message_types_by_name['BidMessage'] = _BIDMESSAGE DESCRIPTOR.message_types_by_name['BidMessage'] = _BIDMESSAGE
DESCRIPTOR.message_types_by_name['BidAcceptMessage'] = _BIDACCEPTMESSAGE DESCRIPTOR.message_types_by_name['BidAcceptMessage'] = _BIDACCEPTMESSAGE
DESCRIPTOR.message_types_by_name['XmrOfferMessage'] = _XMROFFERMESSAGE DESCRIPTOR.message_types_by_name['XmrBidMessage'] = _XMRBIDMESSAGE
DESCRIPTOR.message_types_by_name['XmrSplitMessage'] = _XMRSPLITMESSAGE
DESCRIPTOR.message_types_by_name['XmrBidAcceptMessage'] = _XMRBIDACCEPTMESSAGE
_sym_db.RegisterFileDescriptor(DESCRIPTOR) _sym_db.RegisterFileDescriptor(DESCRIPTOR)
OfferMessage = _reflection.GeneratedProtocolMessageType('OfferMessage', (_message.Message,), { OfferMessage = _reflection.GeneratedProtocolMessageType('OfferMessage', (_message.Message,), {
@ -482,12 +575,26 @@ BidAcceptMessage = _reflection.GeneratedProtocolMessageType('BidAcceptMessage',
}) })
_sym_db.RegisterMessage(BidAcceptMessage) _sym_db.RegisterMessage(BidAcceptMessage)
XmrOfferMessage = _reflection.GeneratedProtocolMessageType('XmrOfferMessage', (_message.Message,), { XmrBidMessage = _reflection.GeneratedProtocolMessageType('XmrBidMessage', (_message.Message,), {
'DESCRIPTOR' : _XMROFFERMESSAGE, 'DESCRIPTOR' : _XMRBIDMESSAGE,
'__module__' : 'messages_pb2'
# @@protoc_insertion_point(class_scope:basicswap.XmrBidMessage)
})
_sym_db.RegisterMessage(XmrBidMessage)
XmrSplitMessage = _reflection.GeneratedProtocolMessageType('XmrSplitMessage', (_message.Message,), {
'DESCRIPTOR' : _XMRSPLITMESSAGE,
'__module__' : 'messages_pb2'
# @@protoc_insertion_point(class_scope:basicswap.XmrSplitMessage)
})
_sym_db.RegisterMessage(XmrSplitMessage)
XmrBidAcceptMessage = _reflection.GeneratedProtocolMessageType('XmrBidAcceptMessage', (_message.Message,), {
'DESCRIPTOR' : _XMRBIDACCEPTMESSAGE,
'__module__' : 'messages_pb2' '__module__' : 'messages_pb2'
# @@protoc_insertion_point(class_scope:basicswap.XmrOfferMessage) # @@protoc_insertion_point(class_scope:basicswap.XmrBidAcceptMessage)
}) })
_sym_db.RegisterMessage(XmrOfferMessage) _sym_db.RegisterMessage(XmrBidAcceptMessage)
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

@ -5,6 +5,7 @@
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import json import json
import decimal
import hashlib import hashlib
from .contrib.segwit_addr import bech32_decode, convertbits, bech32_encode from .contrib.segwit_addr import bech32_decode, convertbits, bech32_encode

@ -1,16 +1,26 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2019 tecnovert # Copyright (c) 2019-2020 tecnovert
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import unittest import unittest
import basicswap.contrib.ed25519_fast as edf
import basicswap.ed25519_fast_util as edu
from basicswap.ecc_util import i2b
from coincurve.ed25519 import ed25519_get_pubkey
from basicswap.util import ( from basicswap.util import (
SerialiseNum, SerialiseNum,
DeserialiseNum, DeserialiseNum,
make_int, make_int,
format8, format8,
format_amount,
validate_amount,
) )
from basicswap.basicswap import ( from basicswap.basicswap import (
Coins, Coins,
@ -134,6 +144,14 @@ class Test(unittest.TestCase):
except Exception as e: except Exception as e:
assert('Too many decimal places' in str(e)) assert('Too many decimal places' in str(e))
def test_ed25519(self):
privkey = edu.get_secret()
pubkey = edu.encodepoint(edf.scalarmult_B(privkey))
privkey_bytes = i2b(privkey)
pubkey_test = ed25519_get_pubkey(privkey_bytes)
assert(pubkey == pubkey_test)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

@ -67,6 +67,8 @@ from tests.basicswap.common import (
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
from bin.basicswap_run import startDaemon from bin.basicswap_run import startDaemon
from pprint import pprint
logger = logging.getLogger() logger = logging.getLogger()
NUM_NODES = 3 NUM_NODES = 3
@ -239,6 +241,7 @@ def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey):
'check_watched_seconds': 4, 'check_watched_seconds': 4,
'check_expired_seconds': 60, 'check_expired_seconds': 60,
'check_events_seconds': 1, 'check_events_seconds': 1,
'check_xmr_swaps_seconds': 1,
'min_delay_auto_accept': 1, 'min_delay_auto_accept': 1,
'max_delay_auto_accept': 5 'max_delay_auto_accept': 5
} }
@ -310,7 +313,7 @@ def run_loop(cls):
btcRpc('generatetoaddress 1 {}'.format(cls.btc_addr)) btcRpc('generatetoaddress 1 {}'.format(cls.btc_addr))
if cls.xmr_addr is not None: if cls.xmr_addr is not None:
callrpc_xmr_na(XMR_BASE_RPC_PORT + 0, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': 1}) callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': 1})
time.sleep(1.0) time.sleep(1.0)
@ -335,6 +338,7 @@ class Test(unittest.TestCase):
logger.propagate = False logger.propagate = False
logger.handlers = [] logger.handlers = []
logger.setLevel(logging.INFO) # DEBUG shows many messages from requests.post logger.setLevel(logging.INFO) # DEBUG shows many messages from requests.post
#logger.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s %(levelname)s : %(message)s') formatter = logging.Formatter('%(asctime)s %(levelname)s : %(message)s')
stream_stdout = logging.StreamHandler() stream_stdout = logging.StreamHandler()
stream_stdout.setFormatter(formatter) stream_stdout.setFormatter(formatter)
@ -417,7 +421,7 @@ class Test(unittest.TestCase):
t.start() t.start()
cls.btc_addr = callnoderpc(0, 'getnewaddress', ['mining_addr', 'bech32'], base_rpc_port=BTC_BASE_RPC_PORT) cls.btc_addr = callnoderpc(0, 'getnewaddress', ['mining_addr', 'bech32'], base_rpc_port=BTC_BASE_RPC_PORT)
cls.xmr_addr = cls.callxmrnodewallet(cls, 0, 'get_address')['address'] cls.xmr_addr = cls.callxmrnodewallet(cls, 1, 'get_address')['address']
num_blocks = 500 num_blocks = 500
logging.info('Mining %d bitcoin blocks to %s', num_blocks, cls.btc_addr) logging.info('Mining %d bitcoin blocks to %s', num_blocks, cls.btc_addr)
@ -425,10 +429,10 @@ class Test(unittest.TestCase):
checkForks(callnoderpc(0, 'getblockchaininfo', base_rpc_port=BTC_BASE_RPC_PORT)) checkForks(callnoderpc(0, 'getblockchaininfo', base_rpc_port=BTC_BASE_RPC_PORT))
if callrpc_xmr_na(XMR_BASE_RPC_PORT + 0, 'get_block_count')['count'] < num_blocks: if callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'get_block_count')['count'] < num_blocks:
logging.info('Mining %d Monero blocks.', num_blocks) logging.info('Mining %d Monero blocks.', num_blocks)
callrpc_xmr_na(XMR_BASE_RPC_PORT + 0, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': num_blocks}) callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': num_blocks})
rv = callrpc_xmr_na(XMR_BASE_RPC_PORT + 0, 'get_block_count') rv = callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'get_block_count')
logging.info('XMR blocks: %d', rv['count']) logging.info('XMR blocks: %d', rv['count'])
logging.info('Starting update thread.') logging.info('Starting update thread.')
@ -509,21 +513,49 @@ class Test(unittest.TestCase):
return return
raise ValueError('wait_for_offer timed out.') raise ValueError('wait_for_offer timed out.')
def wait_for_bid(self, swap_client, bid_id, state=None, sent=False):
logging.info('wait_for_bid %s', bid_id.hex())
for i in range(20):
time.sleep(1)
bids = swap_client.listBids(sent=sent)
for bid in bids:
if bid[1] == bid_id:
if state is not None and state != bid[4]:
continue
return
raise ValueError('wait_for_bid timed out.')
def test_01_part_xmr(self): def test_01_part_xmr(self):
logging.info('---------- Test PART to XMR') logging.info('---------- Test PART to XMR')
swap_clients = self.swap_clients swap_clients = self.swap_clients
js_0 = json.loads(urlopen('http://localhost:1800/json/wallets').read()) js_0 = json.loads(urlopen('http://localhost:1801/json/wallets').read())
assert(make_int(js_0[str(int(Coins.XMR))]['balance'], scale=12) > 0) assert(make_int(js_0[str(int(Coins.XMR))]['balance'], scale=12) > 0)
assert(make_int(js_0[str(int(Coins.XMR))]['unconfirmed'], scale=12) > 0) assert(make_int(js_0[str(int(Coins.XMR))]['unconfirmed'], scale=12) > 0)
offer_id = swap_clients[0].postOffer(Coins.PART, Coins.XMR, 100 * COIN, 10 * XMR_COIN, 100 * COIN, SwapTypes.XMR_SWAP) offer_id = swap_clients[0].postOffer(Coins.PART, Coins.XMR, 100 * COIN, 10 * XMR_COIN, 100 * COIN, SwapTypes.XMR_SWAP)
self.wait_for_offer(swap_clients[1], offer_id) self.wait_for_offer(swap_clients[1], offer_id)
offers = swap_clients[1].listOffers() offer = swap_clients[1].getOffer(offer_id)
offers = swap_clients[1].listOffers(filters={'offer_id': offer_id})
assert(len(offers) == 1) assert(len(offers) == 1)
for offer in offers: offer = offers[0]
print('offer', offer) pprint(vars(offer))
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
self.wait_for_bid(swap_clients[0], bid_id, BidStates.BID_RECEIVED)
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
assert(xmr_swap)
swap_clients[0].acceptXmrBid(bid_id)
self.wait_for_bid(swap_clients[1], bid_id, BidStates.BID_ACCEPTED, sent=True)
if __name__ == '__main__': if __name__ == '__main__':

Loading…
Cancel
Save