Compare commits

..

20 Commits

Author SHA1 Message Date
tecnovert 0bc3226ed9 build, guix: update packed version 2026-06-09 16:51:09 +02:00
tecnovert 1aa53e38f9 build: raise version to 0.16.5 2026-06-09 16:45:29 +02:00
tecnovert 379eaaf0db test: add test for underfunded itx 2026-06-09 16:42:49 +02:00
tecnovert 304e88646f Merge pull request #497 from tecnovert/fix2
fix: verify follower's script chain lock refund tx sig
2026-06-09 14:32:31 +00:00
tecnovert 597cdcbff5 fix: verify follower's script chain lock refund tx sig 2026-06-09 14:51:50 +02:00
tecnovert 176fc48ba2 Merge pull request #494 from basicswap/cryptoguard-patch-2
Update release-notes.md
2026-06-09 12:48:51 +00:00
tecnovert cf836878fd Merge branch 'dev' into cryptoguard-patch-2 2026-06-09 12:12:45 +00:00
tecnovert 37d564b4f7 Update release-notes.md
The PTX amount is already checked in getLockTxHeight.
Tested in test_run.py, test_10_bad_ptx.
2026-06-09 12:10:16 +00:00
tecnovert d2cbce0de9 Merge pull request #496 from tecnovert/strict_swap_types
Reject secret-hash offers where coin pair can use adaptor-sig
2026-06-09 12:00:00 +00:00
tecnovert 3aacc57f09 feat: reject secret-hash offers where coin pair can use adaptor sig 2026-06-09 12:05:31 +02:00
tecnovert d23665d585 build: update docker base images to debian:trixie 2026-06-09 12:03:02 +02:00
dependabot[bot] 553b5a6a32 build(deps-dev): bump black from 26.3.1 to 26.5.1
Bumps [black](https://github.com/psf/black) from 26.3.1 to 26.5.1.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/26.3.1...26.5.1)

---
updated-dependencies:
- dependency-name: black
  dependency-version: 26.5.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 11:01:32 +02:00
Cryptoguard 827909b322 Update release-notes.md 2026-06-08 20:57:24 -04:00
tecnovert 3af05ea5c0 build, guix: update packed version 2026-06-09 02:20:08 +02:00
tecnovert 136b311dc6 build: raise black version 2026-06-09 02:14:44 +02:00
tecnovert df672d4056 build: raise min python version to 3.11 2026-06-09 02:06:22 +02:00
tecnovert f536f8962e test: raise python ci version to 3.14 2026-06-09 02:00:42 +02:00
tecnovert ce5ffe92b4 build: raise version to 0.16.4 2026-06-09 01:51:38 +02:00
tecnovert 32bdd11853 Merge pull request #493 from tecnovert/fix
fix: always require secret hash swap ITX index and value
2026-06-08 23:49:04 +00:00
tecnovert 5e7dbbb22f fix: always require secret hash swap ITX index and value 2026-06-09 01:48:28 +02:00
23 changed files with 258 additions and 124 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
python-version: ["3.12"] python-version: ["3.14"]
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
+10 -5
View File
@@ -1,20 +1,25 @@
FROM ubuntu:22.04 FROM debian:trixie-slim
ENV LANG=C.UTF-8 \ ENV LANG=C.UTF-8 \
DEBIAN_FRONTEND=noninteractive \ DEBIAN_FRONTEND=noninteractive \
DATADIRS="/coindata" DATADIRS="/coindata" \
VIRTUAL_ENV=/opt/venv
RUN apt-get update; \ RUN apt-get update; \
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
python3-pip libpython3-dev gnupg pkg-config gcc libc-dev gosu tzdata cmake ninja-build; python3-pip libpython3-dev python3-venv gnupg pkg-config gcc libc-dev gosu tzdata cmake ninja-build;
# Create python venv
RUN python3 -m venv $VIRTUAL_ENV
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
# Install requirements first so as to skip in subsequent rebuilds # Install requirements first so as to skip in subsequent rebuilds
COPY ./requirements.txt requirements.txt COPY ./requirements.txt requirements.txt
RUN pip3 install -r requirements.txt --require-hashes RUN pip install -r requirements.txt --require-hashes
COPY . basicswap-master COPY . basicswap-master
RUN cd basicswap-master; \ RUN cd basicswap-master; \
pip3 install .; pip install .;
RUN useradd -ms /bin/bash swap_user && \ RUN useradd -ms /bin/bash swap_user && \
mkdir /coindata && chown swap_user -R /coindata mkdir /coindata && chown swap_user -R /coindata
+1 -1
View File
@@ -1,3 +1,3 @@
name = "basicswap" name = "basicswap"
__version__ = "0.16.3" __version__ = "0.16.5"
+83 -39
View File
@@ -3611,6 +3611,16 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
raise ValueError( raise ValueError(
f"Invalid swap type for: {coin_from.name} -> {coin_to.name}" f"Invalid swap type for: {coin_from.name} -> {coin_to.name}"
) )
strict_swap_type: bool = self.settings.get(
"strict_swap_type", False if self.chain == "regtest" else True
)
if strict_swap_type and (
coin_from not in self.coins_without_segwit
or coin_to not in self.coins_without_segwit
):
raise ValueError(
f"Coin pair should use adaptor sig swap type: {coin_from.name} -> {coin_to.name}"
)
def _process_notification_safe(self, event_type, event_data) -> None: def _process_notification_safe(self, event_type, event_data) -> None:
try: try:
@@ -6896,7 +6906,16 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
self.log.debug( self.log.debug(
f"Create initiate txn for coin {ci.coin_name()} to {addr_to} for bid {self.log.id(bid_id)}" f"Create initiate txn for coin {ci.coin_name()} to {addr_to} for bid {self.log.id(bid_id)}"
) )
txn_signed = ci.createRawSignedTransaction(addr_to, bid.amount) amount_from: int = bid.amount
if bid.debug_ind == DebugTypes.MAKE_INVALID_ITX:
amount_from -= 100
self.logBidEvent(
bid.bid_id,
EventLogTypes.DEBUG_TWEAK_APPLIED,
f"Make invalid ITx for testing: {bid.debug_ind}",
None,
)
txn_signed = ci.createRawSignedTransaction(addr_to, amount_from)
txjs = ci.describeTx(txn_signed) txjs = ci.describeTx(txn_signed)
vout = getVoutByAddress(txjs, addr_to) vout = getVoutByAddress(txjs, addr_to)
@@ -6987,13 +7006,10 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
if bid.debug_ind == DebugTypes.MAKE_INVALID_PTX: if bid.debug_ind == DebugTypes.MAKE_INVALID_PTX:
amount_to -= 1 amount_to -= 1
self.log.debug(
f"bid {self.log.id(bid_id)}: Make invalid PTx for testing: {bid.debug_ind}."
)
self.logBidEvent( self.logBidEvent(
bid.bid_id, bid.bid_id,
EventLogTypes.DEBUG_TWEAK_APPLIED, EventLogTypes.DEBUG_TWEAK_APPLIED,
"ind {}".format(bid.debug_ind), f"Make invalid PTx for testing: {bid.debug_ind}",
None, None,
) )
@@ -8421,12 +8437,10 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
# Verify amount # Verify amount
vout = getVoutByAddress(initiate_txn, p2sh) vout = getVoutByAddress(initiate_txn, p2sh)
out_value = make_int(initiate_txn["vout"][vout]["value"]) out_value: int = make_int(initiate_txn["vout"][vout]["value"])
ensure( ensure(
out_value == int(bid.amount), out_value == int(bid.amount),
"Incorrect output amount in initiate txn {}: {} != {}.".format( f"Incorrect output amount in initiate txn {self.logIDT(initiate_txnid_hex)}: {out_value} != {bid.amount}",
initiate_txnid_hex, out_value, int(bid.amount)
),
) )
bid.initiate_tx.conf = initiate_txn["confirmations"] bid.initiate_tx.conf = initiate_txn["confirmations"]
@@ -8454,21 +8468,21 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
) )
index = None index = None
if found: if found:
if ( if "index" not in found:
"value" in found
and found["value"] is not None
and found["value"] != int(bid.amount)
):
self.setBidError( self.setBidError(
bid, bid,
"Incorrect output amount in initiate txn {}: {} != {}.".format( f"Swap output index not found for initiate txn {self.logIDT(initiate_txnid_hex)}",
initiate_txnid_hex, found["value"], int(bid.amount) )
), return True
txo_value: int = found.get("value", None)
if txo_value != bid.amount:
self.setBidError(
bid,
f"Incorrect output amount in initiate txn {self.logIDT(initiate_txnid_hex)}: {txo_value} != {bid.amount}",
) )
return True return True
bid.initiate_tx.conf = found["depth"] bid.initiate_tx.conf = found["depth"]
if "index" in found: index = found["index"]
index = found["index"]
tx_height = found["height"] tx_height = found["height"]
if bid.initiate_tx.conf != last_initiate_txn_conf: if bid.initiate_tx.conf != last_initiate_txn_conf:
@@ -8554,6 +8568,21 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
vout=participate_txvout, vout=participate_txvout,
) )
if found: if found:
participate_txid_hex: str = found.get(
"txid",
None if participate_txid is None else participate_txid.hex(),
)
# Double check value
txo_value: int = found.get("value", None)
if (
txo_value != bid.amount_to
and bid.debug_ind != DebugTypes.MAKE_INVALID_PTX
):
self.setBidError(
bid,
f"Incorrect output amount in participate txn {self.logIDT(participate_txid_hex)}: {txo_value} != {bid.amount_to}",
)
return True
index = found.get("index", participate_txvout) index = found.get("index", participate_txvout)
if bid.participate_tx.conf != found["depth"]: if bid.participate_tx.conf != found["depth"]:
save_bid = True save_bid = True
@@ -8561,15 +8590,16 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
bid.participate_tx.conf is None bid.participate_tx.conf is None
and bid.participate_tx.state != TxStates.TX_SENT and bid.participate_tx.state != TxStates.TX_SENT
): ):
txid = found.get(
"txid",
None if participate_txid is None else participate_txid.hex(),
)
self.log.debug( self.log.debug(
f"Found bid {self.log.id(bid_id)} participate txn {self.log.id(txid)} in chain {ci_to.coin_name()}." f"Found bid {self.log.id(bid_id)} participate txn {self.logIDT(participate_txid_hex)} in chain {ci_to.coin_name()}."
) )
self.addParticipateTxn( self.addParticipateTxn(
bid_id, bid, coin_to, txid, index, found["height"] bid_id,
bid,
coin_to,
participate_txid_hex,
index,
found["height"],
) )
# Only update tx state if tx hasn't already been seen # Only update tx state if tx hasn't already been seen
@@ -8587,7 +8617,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
if bid.participate_tx.conf is not None: if bid.participate_tx.conf is not None:
self.log.debug( self.log.debug(
f"participate txid {self.log.id(bid.participate_tx.txid)} confirms {bid.participate_tx.conf}." f"Participate txid {self.logIDT(bid.participate_tx.txid)} confirms {bid.participate_tx.conf}."
) )
if ( if (
bid.participate_tx.conf bid.participate_tx.conf
@@ -11647,8 +11677,11 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
txid_hex = ci_from.publishTx(lock_tx_signed) txid_hex = ci_from.publishTx(lock_tx_signed)
if txid_hex != b2h(xmr_swap.a_lock_tx_id): if txid_hex != b2h(xmr_swap.a_lock_tx_id):
if not self.isBchXmrSwap(offer):
raise ValueError("Coin A lock tx txid changed after sending!")
self.log.info( self.log.info(
"Recomputing refund transactions and txids after lock tx publish." f"Recomputing {ci_from.coin_name()} refund transactions and txids after lock tx publish."
) )
xmr_swap.a_lock_tx = lock_tx_signed xmr_swap.a_lock_tx = lock_tx_signed
xmr_swap.a_lock_tx_id = bytes.fromhex(txid_hex) xmr_swap.a_lock_tx_id = bytes.fromhex(txid_hex)
@@ -12440,6 +12473,19 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
) )
if not self.isBchXmrSwap(offer): if not self.isBchXmrSwap(offer):
self.log.info("Checking follower's lock refund tx signature.")
prevout_amount = ci_from.getLockTxSwapOutputValue(bid, xmr_swap)
v = ci_from.verifyTxSig(
xmr_swap.a_lock_refund_tx,
xmr_swap.af_lock_refund_tx_sig,
xmr_swap.pkaf,
0,
xmr_swap.a_lock_tx_script,
prevout_amount,
)
ensure(v, "Invalid coin A lock refund tx leader sig")
xmr_swap_1.addLockRefundSigs(self, xmr_swap, ci_from)
# segwit coins sign the transaction # segwit coins sign the transaction
xmr_swap.af_lock_refund_spend_tx_sig = ci_from.decryptOtVES( xmr_swap.af_lock_refund_spend_tx_sig = ci_from.decryptOtVES(
kbsl, xmr_swap.af_lock_refund_spend_tx_esig kbsl, xmr_swap.af_lock_refund_spend_tx_esig
@@ -12454,7 +12500,16 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
xmr_swap.a_lock_refund_tx_script, xmr_swap.a_lock_refund_tx_script,
prevout_amount, prevout_amount,
) )
self.log.info("Checking follower's lock refund spend tx signature.")
v = ci_from.verifyTxSig(
xmr_swap.a_lock_refund_spend_tx,
xmr_swap.af_lock_refund_spend_tx_sig,
xmr_swap.pkaf,
0,
xmr_swap.a_lock_refund_tx_script,
prevout_amount,
)
ensure(v, "Invalid follower signature for lock refund spend txn")
self.log.debug("Setting lock refund spend tx sigs.") self.log.debug("Setting lock refund spend tx sigs.")
witness_stack = [] witness_stack = []
if coin_from not in (Coins.DCR,): if coin_from not in (Coins.DCR,):
@@ -12472,17 +12527,6 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
) )
ensure(signed_tx, "setTxSignature failed") ensure(signed_tx, "setTxSignature failed")
xmr_swap.a_lock_refund_spend_tx = signed_tx xmr_swap.a_lock_refund_spend_tx = signed_tx
v = ci_from.verifyTxSig(
xmr_swap.a_lock_refund_spend_tx,
xmr_swap.af_lock_refund_spend_tx_sig,
xmr_swap.pkaf,
0,
xmr_swap.a_lock_refund_tx_script,
prevout_amount,
)
ensure(v, "Invalid signature for lock refund spend txn")
xmr_swap_1.addLockRefundSigs(self, xmr_swap, ci_from)
else: else:
# BCH signs the output pkh # BCH signs the output pkh
+1
View File
@@ -232,6 +232,7 @@ class DebugTypes(IntEnum):
BID_DONT_SPEND_COIN_A_LOCK_REFUND2 = auto() # continues BID_DONT_SPEND_COIN_A_LOCK_REFUND2 = auto() # continues
CREATE_INVALID_COIN_B_LOCK = auto() CREATE_INVALID_COIN_B_LOCK = auto()
BUYER_STOP_AFTER_ITX = auto() BUYER_STOP_AFTER_ITX = auto()
MAKE_INVALID_ITX = auto()
MAKE_INVALID_PTX = auto() MAKE_INVALID_PTX = auto()
DONT_SPEND_ITX = auto() DONT_SPEND_ITX = auto()
SKIP_LOCK_TX_REFUND = auto() SKIP_LOCK_TX_REFUND = auto()
+19
View File
@@ -1,3 +1,22 @@
0.16.5
==============
- Updated docker base images to Debian Trixie.
- By default reject secret hash type offers where the coin pair could use adaptor sig swap.
- override with "strict_swap_type" setting.
- Verify follower's script chain lock refund tx sig.
0.16.4
==============
- Security: Always require the initiate tx output index and value for secret hash swaps.
- Strengthens the 0.16.3 fix: the amount check can no longer be skipped when the output value is unavailable; the swap is now rejected (fails closed) instead of proceeding.
- Security: Also double check the participate tx output amount for secret hash swaps.
- Raise minimum Python version to 3.11.
0.16.3 0.16.3
============== ==============
+1 -1
View File
@@ -5,7 +5,7 @@ FROM i_swapclient as install_stage
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=bitcoin --withoutcoins=particl && \ RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=bitcoin --withoutcoins=particl && \
find /coin_bin -name *.tar.gz -delete find /coin_bin -name *.tar.gz -delete
FROM debian:bullseye-slim FROM debian:trixie-slim
COPY --from=install_stage /coin_bin . COPY --from=install_stage /coin_bin .
ENV BITCOIN_DATA /data ENV BITCOIN_DATA /data
+1 -1
View File
@@ -5,7 +5,7 @@ FROM i_swapclient as install_stage
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=bitcoincash --withoutcoins=particl && \ RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=bitcoincash --withoutcoins=particl && \
find /coin_bin -name *.tar.gz -delete find /coin_bin -name *.tar.gz -delete
FROM debian:bullseye-slim FROM debian:trixie-slim
COPY --from=install_stage /coin_bin . COPY --from=install_stage /coin_bin .
ENV BITCOINCASH_DATA /data ENV BITCOINCASH_DATA /data
+1 -1
View File
@@ -5,7 +5,7 @@ FROM i_swapclient as install_stage
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=dash --withoutcoins=particl && \ RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=dash --withoutcoins=particl && \
find /coin_bin -name *.tar.gz -delete find /coin_bin -name *.tar.gz -delete
FROM debian:bullseye-slim FROM debian:trixie-slim
COPY --from=install_stage /coin_bin . COPY --from=install_stage /coin_bin .
ENV DASH_DATA /data ENV DASH_DATA /data
+1 -1
View File
@@ -3,7 +3,7 @@ FROM i_swapclient as install_stage
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=decred --withoutcoins=particl && \ RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=decred --withoutcoins=particl && \
find /coin_bin -name *.tar.gz -delete find /coin_bin -name *.tar.gz -delete
FROM debian:bullseye-slim FROM debian:trixie-slim
COPY --from=install_stage /coin_bin . COPY --from=install_stage /coin_bin .
ENV DCR_DATA /data ENV DCR_DATA /data
+1 -1
View File
@@ -3,7 +3,7 @@ FROM i_swapclient as install_stage
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=dogecoin --withoutcoin=particl && \ RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=dogecoin --withoutcoin=particl && \
find /coin_bin -name *.tar.gz -delete find /coin_bin -name *.tar.gz -delete
FROM debian:bullseye-slim FROM debian:trixie-slim
COPY --from=install_stage /coin_bin . COPY --from=install_stage /coin_bin .
ENV DOGECOIN_DATA /data ENV DOGECOIN_DATA /data
+1 -1
View File
@@ -3,7 +3,7 @@ FROM i_swapclient as install_stage
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=firo --withoutcoins=particl && \ RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=firo --withoutcoins=particl && \
find /coin_bin -name *.tar.gz -delete find /coin_bin -name *.tar.gz -delete
FROM debian:bullseye-slim FROM debian:trixie-slim
COPY --from=install_stage /coin_bin . COPY --from=install_stage /coin_bin .
ENV FIRO_DATA /data ENV FIRO_DATA /data
+1 -1
View File
@@ -3,7 +3,7 @@ FROM i_swapclient as install_stage
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=litecoin --withoutcoin=particl && \ RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=litecoin --withoutcoin=particl && \
find /coin_bin -name *.tar.gz -delete find /coin_bin -name *.tar.gz -delete
FROM debian:bullseye-slim FROM debian:trixie-slim
COPY --from=install_stage /coin_bin . COPY --from=install_stage /coin_bin .
ENV LITECOIN_DATA /data ENV LITECOIN_DATA /data
+1 -1
View File
@@ -2,7 +2,7 @@ FROM i_swapclient as install_stage
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=monero --withoutcoins=particl RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=monero --withoutcoins=particl
FROM debian:bullseye-slim FROM debian:trixie-slim
COPY --from=install_stage /coin_bin . COPY --from=install_stage /coin_bin .
+1 -1
View File
@@ -3,7 +3,7 @@ FROM i_swapclient as install_stage
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=particl && \ RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=particl && \
find /coin_bin -name *.tar.gz -delete find /coin_bin -name *.tar.gz -delete
FROM debian:bullseye-slim FROM debian:trixie-slim
COPY --from=install_stage /coin_bin . COPY --from=install_stage /coin_bin .
ENV PARTICL_DATA /data ENV PARTICL_DATA /data
+1 -1
View File
@@ -3,7 +3,7 @@ FROM i_swapclient as install_stage
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=pivx --withoutcoins=particl && \ RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=pivx --withoutcoins=particl && \
find /coin_bin -name *.tar.gz -delete find /coin_bin -name *.tar.gz -delete
FROM debian:bullseye-slim FROM debian:trixie-slim
COPY --from=install_stage /coin_bin . COPY --from=install_stage /coin_bin .
ENV PIVX_DATA /data ENV PIVX_DATA /data
+8 -3
View File
@@ -1,12 +1,17 @@
FROM debian:bullseye-slim FROM debian:trixie-slim
ENV LANG=C.UTF-8 \ ENV LANG=C.UTF-8 \
DEBIAN_FRONTEND=noninteractive \ DEBIAN_FRONTEND=noninteractive \
DATADIR=/data DATADIR=/data \
VIRTUAL_ENV=/opt/venv
RUN apt-get update; \ RUN apt-get update; \
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
python3-pip libpython3-dev gnupg pkg-config gcc libc-dev gosu tzdata wget unzip cmake ninja-build; python3-pip libpython3-dev python3-venv gnupg pkg-config gcc libc-dev gosu tzdata wget unzip cmake ninja-build;
# Create python venv
RUN python3 -m venv $VIRTUAL_ENV
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
ARG BASICSWAP_URL=https://github.com/basicswap/basicswap/archive/master.zip ARG BASICSWAP_URL=https://github.com/basicswap/basicswap/archive/master.zip
ARG BASICSWAP_DIR=basicswap-master ARG BASICSWAP_DIR=basicswap-master
+1 -1
View File
@@ -2,7 +2,7 @@ FROM i_swapclient as install_stage
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=wownero --withoutcoins=particl RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=wownero --withoutcoins=particl
FROM debian:bullseye-slim FROM debian:trixie-slim
COPY --from=install_stage /coin_bin . COPY --from=install_stage /coin_bin .
+3 -3
View File
@@ -135,15 +135,15 @@
(define-public basicswap (define-public basicswap
(package (package
(name "basicswap") (name "basicswap")
(version "0.16.2") (version "0.16.5")
(source (origin (source (origin
(method git-fetch) (method git-fetch)
(uri (git-reference (uri (git-reference
(url "https://github.com/basicswap/basicswap") (url "https://github.com/basicswap/basicswap")
(commit "ced017ab3a3234c68d3d8f773cf9ceb187a39adb"))) (commit "1aa53e38f96ffa753cc6eeaee1cc9fccbd0ce5dd")))
(sha256 (sha256
(base32 (base32
"0manck3zlf05by08b825ynqk7q1byzgy7p3i8chpg413mqkx7q5r")) "0k2r16f0imyzh0x90a2a37m41imnd183vdlf9b8nrx4l884h543y"))
(file-name (git-file-name name version)))) (file-name (git-file-name name version))))
(build-system pyproject-build-system) (build-system pyproject-build-system)
+2 -2
View File
@@ -8,7 +8,7 @@ description = "Simple atomic swap system"
keywords = ["crypto", "cryptocurrency", "particl", "bitcoin", "monero", "wownero"] keywords = ["crypto", "cryptocurrency", "particl", "bitcoin", "monero", "wownero"]
readme = "README.md" readme = "README.md"
license = {file = "LICENSE"} license = {file = "LICENSE"}
requires-python = ">=3.9" requires-python = ">=3.11"
classifiers = [ classifiers = [
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License", "License :: OSI Approved :: MIT License",
@@ -36,7 +36,7 @@ dev = [
"pre-commit", "pre-commit",
"pytest", "pytest",
"ruff", "ruff",
"black==25.11.0", "black==26.5.1",
"selenium", "selenium",
] ]
+78 -1
View File
@@ -8,6 +8,7 @@
import hashlib import hashlib
import logging import logging
import os
import random import random
import secrets import secrets
import threading import threading
@@ -22,10 +23,15 @@ from coincurve.ecdsaotves import (
) )
from coincurve.keys import PrivateKey from coincurve.keys import PrivateKey
from basicswap.basicswap import (
Coins,
BasicSwap,
SwapTypes,
)
from basicswap.contrib.mnemonic import Mnemonic from basicswap.contrib.mnemonic import Mnemonic
from basicswap.db import create_db_, DBMethods, KnownIdentity from basicswap.db import create_db_, DBMethods, KnownIdentity
from basicswap.util import h2b from basicswap.util import h2b
from basicswap.util.address import decodeAddress from basicswap.util.address import decodeAddress, toWIF
from basicswap.util.crypto import ripemd160, hash160, blake256 from basicswap.util.crypto import ripemd160, hash160, blake256
from basicswap.util.extkey import ExtKeyPair from basicswap.util.extkey import ExtKeyPair
from basicswap.util.integer import encode_varint, decode_varint from basicswap.util.integer import encode_varint, decode_varint
@@ -60,6 +66,9 @@ from basicswap.contrib.test_framework.messages import (
CTxOut, CTxOut,
uint256_from_str, uint256_from_str,
) )
from tests.basicswap.common import (
PREFIX_SECRET_KEY_REGTEST,
)
logger = logging.getLogger() logger = logging.getLogger()
@@ -790,6 +799,74 @@ class Test(unittest.TestCase):
== "252cd6e85b99e0fd554c44d5fe638923f7ef563048362406a665cf3400feb1bd" == "252cd6e85b99e0fd554c44d5fe638923f7ef563048362406a665cf3400feb1bd"
) )
def test_validateSwapType(self):
logging.info("---------- Test validateSwapType")
basicswap_dir = "/tmp/bsx_test_other"
if not os.path.exists(basicswap_dir):
os.makedirs(basicswap_dir)
k = PrivateKey()
settings = {
"network_key": toWIF(PREFIX_SECRET_KEY_REGTEST, k.secret),
"network_pubkey": k.public_key.format().hex(),
}
sc = BasicSwap(
basicswap_dir,
settings,
"regtest",
log_name="bsx_test_other",
)
should_pass = [
(Coins.BTC, Coins.XMR, SwapTypes.XMR_SWAP),
(Coins.XMR, Coins.BTC, SwapTypes.XMR_SWAP),
(Coins.BTC, Coins.FIRO, SwapTypes.XMR_SWAP),
(Coins.FIRO, Coins.BTC, SwapTypes.XMR_SWAP),
(Coins.PIVX, Coins.BTC, SwapTypes.SELLER_FIRST),
(Coins.BTC, Coins.PIVX, SwapTypes.SELLER_FIRST),
(Coins.DASH, Coins.PIVX, SwapTypes.SELLER_FIRST),
(Coins.PIVX, Coins.DASH, SwapTypes.SELLER_FIRST),
]
should_fail = [
(Coins.BTC, Coins.XMR, SwapTypes.SELLER_FIRST),
(Coins.XMR, Coins.PART_ANON, SwapTypes.XMR_SWAP),
(Coins.FIRO, Coins.PART_ANON, SwapTypes.XMR_SWAP),
(Coins.PART_ANON, Coins.FIRO, SwapTypes.XMR_SWAP),
(Coins.FIRO, Coins.BTC, SwapTypes.SELLER_FIRST),
(Coins.BTC, Coins.FIRO, SwapTypes.SELLER_FIRST),
]
for case in should_pass:
sc.validateSwapType(case[0], case[1], case[2])
for case in should_fail:
self.assertRaises(
ValueError, sc.validateSwapType, case[0], case[1], case[2]
)
sc.chain = "mainnet"
for case in should_pass:
try:
sc.validateSwapType(case[0], case[1], case[2])
except Exception as e:
assert "Coin pair should use adaptor sig swap type" in str(e)
else:
if case[2] != SwapTypes.XMR_SWAP:
if (
case[0] not in sc.coins_without_segwit
or case[1] not in sc.coins_without_segwit
):
raise ValueError(f"Invalid swap pair in strict mode {case}")
for case in should_fail:
self.assertRaises(
ValueError, sc.validateSwapType, case[0], case[1], case[2]
)
sc.settings["strict_swap_type"] = False
for case in should_pass:
sc.validateSwapType(case[0], case[1], case[2])
del sc
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
+39 -52
View File
@@ -26,11 +26,13 @@ from basicswap.basicswap import (
TxStates, TxStates,
) )
from basicswap.basicswap_util import ( from basicswap.basicswap_util import (
EventLogTypes,
TxLockTypes, TxLockTypes,
) )
from basicswap.chainparams import ( from basicswap.chainparams import (
chainparams, chainparams,
) )
from basicswap.db import Concepts
from basicswap.util import ( from basicswap.util import (
COIN, COIN,
make_int, make_int,
@@ -47,6 +49,7 @@ from tests.basicswap.common import (
wait_for_balance, wait_for_balance,
wait_for_bid, wait_for_bid,
wait_for_bid_tx_state, wait_for_bid_tx_state,
wait_for_event,
wait_for_in_progress, wait_for_in_progress,
wait_for_offer, wait_for_offer,
wait_for_unspent, wait_for_unspent,
@@ -159,35 +162,6 @@ class Test(BaseTest):
rv = read_json_api(1800, "rateslist?from=PART&to=BTC") rv = read_json_api(1800, "rateslist?from=PART&to=BTC")
assert len(rv) == 1 assert len(rv) == 1
def test_004_validateSwapType(self):
logging.info("---------- Test validateSwapType")
sc = self.swap_clients[0]
should_pass = [
(Coins.BTC, Coins.XMR, SwapTypes.XMR_SWAP),
(Coins.XMR, Coins.BTC, SwapTypes.XMR_SWAP),
(Coins.BTC, Coins.FIRO, SwapTypes.XMR_SWAP),
(Coins.FIRO, Coins.BTC, SwapTypes.XMR_SWAP),
(Coins.PIVX, Coins.BTC, SwapTypes.SELLER_FIRST),
(Coins.BTC, Coins.PIVX, SwapTypes.SELLER_FIRST),
]
should_fail = [
(Coins.BTC, Coins.XMR, SwapTypes.SELLER_FIRST),
(Coins.XMR, Coins.PART_ANON, SwapTypes.XMR_SWAP),
(Coins.FIRO, Coins.PART_ANON, SwapTypes.XMR_SWAP),
(Coins.PART_ANON, Coins.FIRO, SwapTypes.XMR_SWAP),
(Coins.FIRO, Coins.BTC, SwapTypes.SELLER_FIRST),
(Coins.BTC, Coins.FIRO, SwapTypes.SELLER_FIRST),
]
for case in should_pass:
sc.validateSwapType(case[0], case[1], case[2])
for case in should_fail:
self.assertRaises(
ValueError, sc.validateSwapType, case[0], case[1], case[2]
)
def test_003_cltv(self): def test_003_cltv(self):
test_coin_from = Coins.PART test_coin_from = Coins.PART
logging.info("---------- Test {} cltv".format(test_coin_from.name)) logging.info("---------- Test {} cltv".format(test_coin_from.name))
@@ -879,7 +853,6 @@ class Test(BaseTest):
swap_clients = self.swap_clients swap_clients = self.swap_clients
swap_value = make_int(random.uniform(0.001, 10.0), scale=8, r=1) swap_value = make_int(random.uniform(0.001, 10.0), scale=8, r=1)
logging.info("swap_value {}".format(format_amount(swap_value, 8)))
offer_id = swap_clients[0].postOffer( offer_id = swap_clients[0].postOffer(
Coins.LTC, Coins.LTC,
Coins.BTC, Coins.BTC,
@@ -908,15 +881,15 @@ class Test(BaseTest):
) )
wait_for_bid( wait_for_bid(
test_delay_event, test_delay_event,
swap_clients[1], swap_clients[0],
bid_id, bid_id,
BidStates.SWAP_COMPLETED, BidStates.SWAP_COMPLETED,
sent=True, sent=True,
wait_for=30, wait_for=30,
) )
js_0_bid = read_json_api(1800, "bids/{}".format(bid_id.hex())) js_0_bid = read_json_api(1800, f"bids/{bid_id.hex()}")
js_1_bid = read_json_api(1801, "bids/{}".format(bid_id.hex())) js_1_bid = read_json_api(1801, f"bids/{bid_id.hex()}")
assert js_0_bid["itx_state"] == "Refunded" assert js_0_bid["itx_state"] == "Refunded"
assert js_1_bid["ptx_state"] == "Refunded" assert js_1_bid["ptx_state"] == "Refunded"
@@ -933,38 +906,52 @@ class Test(BaseTest):
assert compare_bid_states(offerer_states, self.states_offerer_sh[1]) is True assert compare_bid_states(offerer_states, self.states_offerer_sh[1]) is True
assert compare_bid_states(bidder_states, self.states_bidder_sh[1]) is True assert compare_bid_states(bidder_states, self.states_bidder_sh[1]) is True
""" def test_11_bad_itx(self):
def test_11_refund(self): # Invalid ITx sent, PTx should not be sent
# Seller submits initiate txn, buyer doesn't respond, repeat of test 5 using debug_ind logging.info("---------- Test bad ITx, LTC to BTC")
logging.info('---------- Test refund, LTC to BTC')
swap_clients = self.swap_clients swap_clients = self.swap_clients
swap_value = make_int(random.uniform(0.001, 10.0), scale=8, r=1) swap_value = make_int(random.uniform(0.001, 10.0), scale=8, r=1)
logging.info('swap_value {}'.format(format_amount(swap_value, 8))) offer_id = swap_clients[0].postOffer(
offer_id = swap_clients[0].postOffer(Coins.LTC, Coins.BTC, swap_value, 0.1 * COIN, swap_value, SwapTypes.SELLER_FIRST, Coins.LTC,
TxLockTypes.SEQUENCE_LOCK_BLOCKS, 10) Coins.BTC,
swap_value,
0.1 * COIN,
swap_value,
SwapTypes.SELLER_FIRST,
TxLockTypes.SEQUENCE_LOCK_BLOCKS,
18,
)
wait_for_offer(test_delay_event, swap_clients[1], offer_id) wait_for_offer(test_delay_event, swap_clients[1], offer_id)
offer = swap_clients[1].getOffer(offer_id) offer = swap_clients[1].getOffer(offer_id)
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from) bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.BUYER_STOP_AFTER_ITX)
wait_for_bid(test_delay_event, swap_clients[0], bid_id) wait_for_bid(test_delay_event, swap_clients[0], bid_id)
swap_clients[0].setBidDebugInd(bid_id, DebugTypes.MAKE_INVALID_ITX)
swap_clients[0].acceptBid(bid_id) swap_clients[0].acceptBid(bid_id)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=120) event = wait_for_event(
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.BID_ABANDONED, sent=True, wait_for=30) test_delay_event,
swap_clients[1],
Concepts.BID,
bid_id,
event_type=EventLogTypes.ERROR,
wait_for=120,
)
assert "Incorrect output amount in initiate txn " in event.event_msg
js_0_bid = read_json_api(1800, 'bids/{}'.format(bid_id.hex())) wait_for_bid(
js_1_bid = read_json_api(1801, 'bids/{}'.format(bid_id.hex())) test_delay_event,
assert (js_0_bid['itx_state'] == 'Refunded') swap_clients[1],
assert (js_1_bid['ptx_state'] == 'Unknown') bid_id,
BidStates.BID_ERROR,
sent=True,
wait_for=30,
)
js_0 = read_json_api(1800) js_1_bid = read_json_api(1801, f"bids/{bid_id.hex()}")
js_1 = read_json_api(1801) assert js_1_bid["ptx_state"] == "Unknown"
assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0)
"""
def test_12_withdrawal(self): def test_12_withdrawal(self):
logging.info("---------- Test LTC withdrawals") logging.info("---------- Test LTC withdrawals")
+2 -6
View File
@@ -23,9 +23,7 @@ from copy import deepcopy
from coincurve.keys import PrivateKey from coincurve.keys import PrivateKey
import basicswap.config as cfg import basicswap.config as cfg
from basicswap.db import ( from basicswap.db import Concepts
Concepts,
)
from basicswap.basicswap import ( from basicswap.basicswap import (
Coins, Coins,
BasicSwap, BasicSwap,
@@ -38,9 +36,7 @@ from basicswap.basicswap_util import (
EventLogTypes, EventLogTypes,
) )
from basicswap.util import COIN, format_amount, make_int, TemporaryError from basicswap.util import COIN, format_amount, make_int, TemporaryError
from basicswap.util.address import ( from basicswap.util.address import toWIF
toWIF,
)
from basicswap.rpc import ( from basicswap.rpc import (
callrpc, callrpc,
) )