diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml index 75b3589..539956d 100644 --- a/.github/workflows/unittests.yml +++ b/.github/workflows/unittests.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: os: [macos-13, ubuntu-latest] - python-version: ["3.8", "3.12"] + python-version: ["3.9", "3.12"] bitcoind-version: ["29.2"] steps: diff --git a/pyproject.toml b/pyproject.toml index 8f0e581..3129ef4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "joinmarket" version = "0.9.12dev" description = "Joinmarket client library for Bitcoin coinjoins" readme = "README.md" -requires-python = ">=3.8,<3.13" +requires-python = ">=3.9,<3.13" license = {file = "LICENSE"} dependencies = [ "chromalog==1.0.5", @@ -24,7 +24,7 @@ jmbitcoin = [ jmclient = [ "argon2_cffi==21.3.0", "autobahn==20.12.3", - "bencoder.pyx==3.0.1", + "fastbencode==0.3.6", "klein==20.6.0", "mnemonic==0.20", "pyjwt==2.4.0", diff --git a/src/jmclient/storage.py b/src/jmclient/storage.py index 4658edf..d685422 100644 --- a/src/jmclient/storage.py +++ b/src/jmclient/storage.py @@ -1,10 +1,14 @@ +import atexit import os import shutil -import atexit -import bencoder from hashlib import sha256 +from typing import Any + from argon2 import low_level -from jmbase import aes_cbc_encrypt, aes_cbc_decrypt +from fastbencode import bdecode, bencode_utf8 + +from jmbase import aes_cbc_decrypt, aes_cbc_encrypt + from .support import get_random_bytes @@ -223,12 +227,12 @@ class Storage(object): return self.path @staticmethod - def _serialize(data): - return bencoder.bencode(data) + def _serialize(data: Any) -> bytes: + return bencode_utf8(data) @staticmethod - def _deserialize(data): - return bencoder.bdecode(data) + def _deserialize(data: bytes) -> Any: + return bdecode(data) def _encrypt_file(self, data): if not self.is_encrypted(): diff --git a/test/jmclient/test_storage.py b/test/jmclient/test_storage.py index a8a2925..2f0e465 100644 --- a/test/jmclient/test_storage.py +++ b/test/jmclient/test_storage.py @@ -1,4 +1,3 @@ - from jmclient import storage import pytest @@ -81,6 +80,7 @@ def test_storage_invalid(): MockStorage(b'garbagefile', __file__, b'password') pytest.fail("Non-wallet file, encrypted") + def test_storage_readonly(): s = MockStorage(None, 'nonexistant', b'password', create=True) s = MockStorage(s.file_data, __file__, b'password', read_only=True) @@ -136,3 +136,30 @@ def test_storage_lock(tmpdir): s._create_lock() pytest.fail("It should not be possible to re-create a lock") + +testdata = { + b"bytes_key": b"bytes_value", + b"int_key": 42, + b"list_key": [b"a", b"b", b"c", 1, 2, 3], + b"dict_key": {b"nested": b"data", b"number": 999}, +} + + +@pytest.mark.parametrize( + "test_data, expected_out", + [ + (testdata, testdata), + ({b"dict_key": {b"utf": "value"}}, {b"dict_key": {b"utf": b"value"}}), + ], +) +def test_bencode_roundtrip_consistency(test_data, expected_out): + s = MockStorage(None, "nonexistent", None, create=True) + + serialized1 = s._serialize(test_data) + deserialized1 = s._deserialize(serialized1) + + serialized2 = s._serialize(deserialized1) + deserialized2 = s._deserialize(serialized2) + + assert serialized1 == serialized2 + assert deserialized1 == deserialized2 == expected_out