Compare commits

...

14 Commits

Author SHA1 Message Date
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
22 changed files with 178 additions and 80 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.12"]
python-version: ["3.14"]
steps:
- uses: actions/checkout@v6
- 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 \
DEBIAN_FRONTEND=noninteractive \
DATADIRS="/coindata"
DATADIRS="/coindata" \
VIRTUAL_ENV=/opt/venv
RUN apt-get update; \
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
COPY ./requirements.txt requirements.txt
RUN pip3 install -r requirements.txt --require-hashes
RUN pip install -r requirements.txt --require-hashes
COPY . basicswap-master
RUN cd basicswap-master; \
pip3 install .;
pip install .;
RUN useradd -ms /bin/bash swap_user && \
mkdir /coindata && chown swap_user -R /coindata
+1 -1
View File
@@ -1,3 +1,3 @@
name = "basicswap"
__version__ = "0.16.3"
__version__ = "0.16.4"
+44 -20
View File
@@ -3611,6 +3611,16 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
raise ValueError(
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:
try:
@@ -8421,12 +8431,10 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
# Verify amount
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(
out_value == int(bid.amount),
"Incorrect output amount in initiate txn {}: {} != {}.".format(
initiate_txnid_hex, out_value, int(bid.amount)
),
f"Incorrect output amount in initiate txn {self.logIDT(initiate_txnid_hex)}: {out_value} != {bid.amount}",
)
bid.initiate_tx.conf = initiate_txn["confirmations"]
@@ -8454,20 +8462,20 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
)
index = None
if found:
if (
"value" in found
and found["value"] is not None
and found["value"] != int(bid.amount)
):
if "index" not in found:
self.setBidError(
bid,
"Incorrect output amount in initiate txn {}: {} != {}.".format(
initiate_txnid_hex, found["value"], int(bid.amount)
),
f"Swap output index not found for initiate txn {self.logIDT(initiate_txnid_hex)}",
)
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
bid.initiate_tx.conf = found["depth"]
if "index" in found:
index = found["index"]
tx_height = found["height"]
@@ -8554,6 +8562,21 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
vout=participate_txvout,
)
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)
if bid.participate_tx.conf != found["depth"]:
save_bid = True
@@ -8561,15 +8584,16 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
bid.participate_tx.conf is None
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(
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(
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
@@ -8587,7 +8611,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
if bid.participate_tx.conf is not None:
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 (
bid.participate_tx.conf
+18
View File
@@ -1,3 +1,21 @@
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.
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
==============
+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 && \
find /coin_bin -name *.tar.gz -delete
FROM debian:bullseye-slim
FROM debian:trixie-slim
COPY --from=install_stage /coin_bin .
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 && \
find /coin_bin -name *.tar.gz -delete
FROM debian:bullseye-slim
FROM debian:trixie-slim
COPY --from=install_stage /coin_bin .
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 && \
find /coin_bin -name *.tar.gz -delete
FROM debian:bullseye-slim
FROM debian:trixie-slim
COPY --from=install_stage /coin_bin .
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 && \
find /coin_bin -name *.tar.gz -delete
FROM debian:bullseye-slim
FROM debian:trixie-slim
COPY --from=install_stage /coin_bin .
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 && \
find /coin_bin -name *.tar.gz -delete
FROM debian:bullseye-slim
FROM debian:trixie-slim
COPY --from=install_stage /coin_bin .
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 && \
find /coin_bin -name *.tar.gz -delete
FROM debian:bullseye-slim
FROM debian:trixie-slim
COPY --from=install_stage /coin_bin .
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 && \
find /coin_bin -name *.tar.gz -delete
FROM debian:bullseye-slim
FROM debian:trixie-slim
COPY --from=install_stage /coin_bin .
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
FROM debian:bullseye-slim
FROM debian:trixie-slim
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 && \
find /coin_bin -name *.tar.gz -delete
FROM debian:bullseye-slim
FROM debian:trixie-slim
COPY --from=install_stage /coin_bin .
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 && \
find /coin_bin -name *.tar.gz -delete
FROM debian:bullseye-slim
FROM debian:trixie-slim
COPY --from=install_stage /coin_bin .
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 \
DEBIAN_FRONTEND=noninteractive \
DATADIR=/data
DATADIR=/data \
VIRTUAL_ENV=/opt/venv
RUN apt-get update; \
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_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
FROM debian:bullseye-slim
FROM debian:trixie-slim
COPY --from=install_stage /coin_bin .
+3 -3
View File
@@ -135,15 +135,15 @@
(define-public basicswap
(package
(name "basicswap")
(version "0.16.2")
(version "0.16.4")
(source (origin
(method git-fetch)
(uri (git-reference
(url "https://github.com/basicswap/basicswap")
(commit "ced017ab3a3234c68d3d8f773cf9ceb187a39adb")))
(commit "136b311dc68f11b9c12ebd6877c5f718d705603a")))
(sha256
(base32
"0manck3zlf05by08b825ynqk7q1byzgy7p3i8chpg413mqkx7q5r"))
"0ikr8ik9rklvafd1j8zj0y38vric02qhmj7pvp3kvzbmd2fxx95p"))
(file-name (git-file-name name version))))
(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"]
readme = "README.md"
license = {file = "LICENSE"}
requires-python = ">=3.9"
requires-python = ">=3.11"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
@@ -36,7 +36,7 @@ dev = [
"pre-commit",
"pytest",
"ruff",
"black==25.11.0",
"black==26.5.1",
"selenium",
]
+78 -1
View File
@@ -8,6 +8,7 @@
import hashlib
import logging
import os
import random
import secrets
import threading
@@ -22,10 +23,15 @@ from coincurve.ecdsaotves import (
)
from coincurve.keys import PrivateKey
from basicswap.basicswap import (
Coins,
BasicSwap,
SwapTypes,
)
from basicswap.contrib.mnemonic import Mnemonic
from basicswap.db import create_db_, DBMethods, KnownIdentity
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.extkey import ExtKeyPair
from basicswap.util.integer import encode_varint, decode_varint
@@ -60,6 +66,9 @@ from basicswap.contrib.test_framework.messages import (
CTxOut,
uint256_from_str,
)
from tests.basicswap.common import (
PREFIX_SECRET_KEY_REGTEST,
)
logger = logging.getLogger()
@@ -790,6 +799,74 @@ class Test(unittest.TestCase):
== "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__":
unittest.main()
-29
View File
@@ -159,35 +159,6 @@ class Test(BaseTest):
rv = read_json_api(1800, "rateslist?from=PART&to=BTC")
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):
test_coin_from = Coins.PART
logging.info("---------- Test {} cltv".format(test_coin_from.name))
+1 -3
View File
@@ -38,9 +38,7 @@ from basicswap.basicswap_util import (
EventLogTypes,
)
from basicswap.util import COIN, format_amount, make_int, TemporaryError
from basicswap.util.address import (
toWIF,
)
from basicswap.util.address import toWIF
from basicswap.rpc import (
callrpc,
)