URI: 
       tMerge pull request #6014 from SomberNight/20200304_pycryptodomex - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit dbd77b7d8e5b4542042bc8a262435f45d77706c0
   DIR parent 8f3fcdd1a8a0c1a16c23455c7db72b1c51bf3e0d
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Thu,  5 Mar 2020 09:17:42 +0100
       
       Merge pull request #6014 from SomberNight/20200304_pycryptodomex
       
       add 'cryptography' as optional dependency; clean README and sdist
       Diffstat:
         M .travis.yml                         |       2 +-
         M README.rst                          |      41 ++++++++++++++++++++++---------
         M contrib/deterministic-build/requir… |      33 +++++++++++++++++++++++++++++++
         M contrib/deterministic-build/requir… |      33 -------------------------------
         M contrib/requirements/requirements-… |       1 +
         M contrib/requirements/requirements.… |       1 -
         M electrum/crypto.py                  |      96 +++++++++++++++++++++++++++++--
         M electrum/lnonion.py                 |       9 ++++-----
         M electrum/lntransport.py             |      20 +++++++++-----------
         M electrum/tests/test_bitcoin.py      |      84 ++++++++++++++++++++++++++-----
         M electrum/tests/test_lnpeer.py       |       4 ++++
         M electrum/tests/test_lnrouter.py     |       5 +++++
         M electrum/tests/test_lntransport.py  |       3 +++
         M setup.py                            |       7 ++++++-
         M tox.ini                             |       2 +-
       
       15 files changed, 259 insertions(+), 82 deletions(-)
       ---
   DIR diff --git a/.travis.yml b/.travis.yml
       t@@ -30,7 +30,7 @@ jobs:
                - sudo apt-get -qq update
                - sudo apt-get install -yq bitcoind
                - sudo apt-get -y install libsecp256k1-0
       -        - pip install -r contrib/requirements/requirements.txt
       +        - pip install .[tests]
                - pip install electrumx
              before_script:
                  - electrum/tests/regtest/start_bitcoind.sh
   DIR diff --git a/README.rst b/README.rst
       t@@ -26,16 +26,26 @@ Electrum - Lightweight Bitcoin client
        Getting started
        ===============
        
       -Electrum itself is pure Python, and so are most of the required dependencies.
       +(*If you've come here looking to simply run Electrum,* `you may download it here`_.)
        
       -Non-python dependencies
       ------------------------
       +.. _you may download it here: https://electrum.org/#download
       +
       +Electrum itself is pure Python, and so are most of the required dependencies,
       +but not everything. The following sections describe how to run from source, but here
       +is a TL;DR::
       +
       +    sudo apt-get install libsecp256k1-0
       +    python3 -m pip install --user .[gui,crypto]
       +
       +
       +Not pure-python dependencies
       +----------------------------
        
        If you want to use the Qt interface, install the Qt dependencies::
        
            sudo apt-get install python3-pyqt5
        
       -For elliptic curve operations, libsecp256k1 is a required dependency::
       +For elliptic curve operations, `libsecp256k1`_ is a required dependency::
        
            sudo apt-get install libsecp256k1-0
        
       t@@ -44,13 +54,26 @@ libsecp256k1 yourself::
        
            ./contrib/make_libsecp256k1.sh
        
       +Due to the need for fast symmetric ciphers, either one of `pycryptodomex`_
       +or `cryptography`_ is required. Install from your package manager
       +(or from pip)::
       +
       +    sudo apt-get install python3-cryptography
       +
       +
       +If you would like hardware wallet support, see `this`_.
       +
       +.. _libsecp256k1: https://github.com/bitcoin-core/secp256k1
       +.. _pycryptodomex: https://github.com/Legrandin/pycryptodome
       +.. _cryptography: https://github.com/pyca/cryptography
       +.. _this: https://github.com/spesmilo/electrum-docs/blob/master/hardware-linux.rst
        
        Running from tar.gz
        -------------------
        
        If you downloaded the official package (tar.gz), you can run
        Electrum from its root directory without installing it on your
       -system; all the python dependencies are included in the 'packages'
       +system; all the pure python dependencies are included in the 'packages'
        directory. To run Electrum from its root directory, just do::
        
            ./run_electrum
       t@@ -63,13 +86,9 @@ You can also install Electrum on your system, by running this command::
        This will download and install the Python dependencies used by
        Electrum instead of using the 'packages' directory.
        
       -If you cloned the git repository, you need to compile extra files
       -before you can run Electrum. Read the next section, "Development
       -version".
        
       -
       -Development version
       --------------------
       +Development version (git clone)
       +-------------------------------
        
        Check out the code from GitHub::
        
   DIR diff --git a/contrib/deterministic-build/requirements-binaries.txt b/contrib/deterministic-build/requirements-binaries.txt
       t@@ -1,6 +1,39 @@
        pip==19.3.1 \
            --hash=sha256:21207d76c1031e517668898a6b46a9fb1501c7a4710ef5dfd6a40ad9e6757ea7 \
            --hash=sha256:6917c65fc3769ecdc61405d3dfd97afdedd75808d200b2838d7d961cebc0c2c7
       +pycryptodomex==3.9.4 \
       +    --hash=sha256:0943b65fb41b7403a9def6214061fdd9ab9afd0bbc581e553c72eebe60bded36 \
       +    --hash=sha256:0a1dbb5c4d975a4ea568fb7686550aa225d94023191fb0cca8747dc5b5d77857 \
       +    --hash=sha256:0f43f1608518347fdcb9c8f443fa5cabedd33f94188b13e4196a3a7ba90d169c \
       +    --hash=sha256:11ce5fec5990e34e3981ed14897ba601c83957b577d77d395f1f8f878a179f98 \
       +    --hash=sha256:17a09e38fdc91e4857cf5a7ce82f3c0b229c3977490f2146513e366923fc256b \
       +    --hash=sha256:22d970cee5c096b9123415e183ae03702b2cd4d3ba3f0ced25c4e1aba3967167 \
       +    --hash=sha256:2a1793efcbae3a2264c5e0e492a2629eb10d895d6e5f17dbbd00eb8b489c6bda \
       +    --hash=sha256:30a8a148a0fe482cec1aaf942bbd0ade56ec197c14fe058b2a94318c57e1f991 \
       +    --hash=sha256:32fbbaf964c5184d3f3e349085b0536dd28184b02e2b014fc900f58bbc126339 \
       +    --hash=sha256:347d67faee36d449dc9632da411cc318df52959079062627f1243001b10dc227 \
       +    --hash=sha256:45f4b4e5461a041518baabc52340c249b60833aa84cea6377dc8016a2b33c666 \
       +    --hash=sha256:4717daec0035034b002d31c42e55431c970e3e38a78211f43990e1b7eaf19e28 \
       +    --hash=sha256:51a1ac9e7dda81da444fed8be558a60ec88dfc73b2aa4b0efa310e87acb75838 \
       +    --hash=sha256:53e9dcc8f14783f6300b70da325a50ac1b0a3dbaee323bd9dc3f71d409c197a1 \
       +    --hash=sha256:5519a2ed776e193688b7ddb61ab709303f6eb7d1237081e298283c72acc44271 \
       +    --hash=sha256:583450e8e80a0885c453211ed2bd69ceea634d8c904f23ff8687f677fe810e95 \
       +    --hash=sha256:60f862bd2a07133585a4fc2ce2b1a8ec24746b07ac44307d22ef2b767cb03435 \
       +    --hash=sha256:612091f1d3c84e723bec7cb855cf77576e646045744794c9a3f75ba80737762f \
       +    --hash=sha256:629a87b87c8203b8789ccefc7f2f2faecd2daaeb56bdd0b4e44cd89565f2db07 \
       +    --hash=sha256:6e56ec4c8938fb388b6f250ddd5e21c15e8f25a76e0ad0e2abae9afee09e67b4 \
       +    --hash=sha256:8e8092651844a11ec7fa534395f3dfe99256ce4edca06f128efc9d770d6e1dc1 \
       +    --hash=sha256:8f5f260629876603e08f3ce95c8ccd9b6b83bf9a921c41409046796267f7adc5 \
       +    --hash=sha256:9a6b74f38613f54c56bd759b411a352258f47489bbefd1d57c930a291498b35b \
       +    --hash=sha256:a5a13ebb52c4cd065fb673d8c94f39f30823428a4de19e1f3f828b63a8882d1e \
       +    --hash=sha256:a77ca778a476829876a3a70ae880073379160e4a465d057e3c4e1c79acdf1b8a \
       +    --hash=sha256:a9f7be3d19f79429c2118fd61bc2ec4fa095e93b56fb3a5f3009822402c4380f \
       +    --hash=sha256:dc15a467c4f9e4b43748ba2f97aea66f67812bfd581818284c47cadc81d4caec \
       +    --hash=sha256:e13cdeea23059f7577c230fd580d2c8178e67ebe10e360041abe86c33c316f1c \
       +    --hash=sha256:e45b85c8521bca6bdfaf57e4987743ade53e9f03529dd3adbc9524094c6d55c4 \
       +    --hash=sha256:e87f17867b260f57c88487f943eb4d46c90532652bb37046e764842c3b66cbb1 \
       +    --hash=sha256:ee40a5b156f6c1192bc3082e9d73d0479904433cdda83110546cd67f5a15a5be \
       +    --hash=sha256:ef63ffde3b267043579af8830fc97fc3b9b8a526a24e3ba23af9989d4e9e689a
        PyQt5==5.11.3 \
            --hash=sha256:517e4339135c4874b799af0d484bc2e8c27b54850113a68eec40a0b56534f450 \
            --hash=sha256:ac1eb5a114b6e7788e8be378be41c5e54b17d5158994504e85e43b5fca006a39 \
   DIR diff --git a/contrib/deterministic-build/requirements.txt b/contrib/deterministic-build/requirements.txt
       t@@ -103,39 +103,6 @@ protobuf==3.11.1 \
            --hash=sha256:e88a924b591b06d0191620e9c8aa75297b3111066bb09d49a24bae1054a10c13
        pyaes==1.6.1 \
            --hash=sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f
       -pycryptodomex==3.9.4 \
       -    --hash=sha256:0943b65fb41b7403a9def6214061fdd9ab9afd0bbc581e553c72eebe60bded36 \
       -    --hash=sha256:0a1dbb5c4d975a4ea568fb7686550aa225d94023191fb0cca8747dc5b5d77857 \
       -    --hash=sha256:0f43f1608518347fdcb9c8f443fa5cabedd33f94188b13e4196a3a7ba90d169c \
       -    --hash=sha256:11ce5fec5990e34e3981ed14897ba601c83957b577d77d395f1f8f878a179f98 \
       -    --hash=sha256:17a09e38fdc91e4857cf5a7ce82f3c0b229c3977490f2146513e366923fc256b \
       -    --hash=sha256:22d970cee5c096b9123415e183ae03702b2cd4d3ba3f0ced25c4e1aba3967167 \
       -    --hash=sha256:2a1793efcbae3a2264c5e0e492a2629eb10d895d6e5f17dbbd00eb8b489c6bda \
       -    --hash=sha256:30a8a148a0fe482cec1aaf942bbd0ade56ec197c14fe058b2a94318c57e1f991 \
       -    --hash=sha256:32fbbaf964c5184d3f3e349085b0536dd28184b02e2b014fc900f58bbc126339 \
       -    --hash=sha256:347d67faee36d449dc9632da411cc318df52959079062627f1243001b10dc227 \
       -    --hash=sha256:45f4b4e5461a041518baabc52340c249b60833aa84cea6377dc8016a2b33c666 \
       -    --hash=sha256:4717daec0035034b002d31c42e55431c970e3e38a78211f43990e1b7eaf19e28 \
       -    --hash=sha256:51a1ac9e7dda81da444fed8be558a60ec88dfc73b2aa4b0efa310e87acb75838 \
       -    --hash=sha256:53e9dcc8f14783f6300b70da325a50ac1b0a3dbaee323bd9dc3f71d409c197a1 \
       -    --hash=sha256:5519a2ed776e193688b7ddb61ab709303f6eb7d1237081e298283c72acc44271 \
       -    --hash=sha256:583450e8e80a0885c453211ed2bd69ceea634d8c904f23ff8687f677fe810e95 \
       -    --hash=sha256:60f862bd2a07133585a4fc2ce2b1a8ec24746b07ac44307d22ef2b767cb03435 \
       -    --hash=sha256:612091f1d3c84e723bec7cb855cf77576e646045744794c9a3f75ba80737762f \
       -    --hash=sha256:629a87b87c8203b8789ccefc7f2f2faecd2daaeb56bdd0b4e44cd89565f2db07 \
       -    --hash=sha256:6e56ec4c8938fb388b6f250ddd5e21c15e8f25a76e0ad0e2abae9afee09e67b4 \
       -    --hash=sha256:8e8092651844a11ec7fa534395f3dfe99256ce4edca06f128efc9d770d6e1dc1 \
       -    --hash=sha256:8f5f260629876603e08f3ce95c8ccd9b6b83bf9a921c41409046796267f7adc5 \
       -    --hash=sha256:9a6b74f38613f54c56bd759b411a352258f47489bbefd1d57c930a291498b35b \
       -    --hash=sha256:a5a13ebb52c4cd065fb673d8c94f39f30823428a4de19e1f3f828b63a8882d1e \
       -    --hash=sha256:a77ca778a476829876a3a70ae880073379160e4a465d057e3c4e1c79acdf1b8a \
       -    --hash=sha256:a9f7be3d19f79429c2118fd61bc2ec4fa095e93b56fb3a5f3009822402c4380f \
       -    --hash=sha256:dc15a467c4f9e4b43748ba2f97aea66f67812bfd581818284c47cadc81d4caec \
       -    --hash=sha256:e13cdeea23059f7577c230fd580d2c8178e67ebe10e360041abe86c33c316f1c \
       -    --hash=sha256:e45b85c8521bca6bdfaf57e4987743ade53e9f03529dd3adbc9524094c6d55c4 \
       -    --hash=sha256:e87f17867b260f57c88487f943eb4d46c90532652bb37046e764842c3b66cbb1 \
       -    --hash=sha256:ee40a5b156f6c1192bc3082e9d73d0479904433cdda83110546cd67f5a15a5be \
       -    --hash=sha256:ef63ffde3b267043579af8830fc97fc3b9b8a526a24e3ba23af9989d4e9e689a
        pyrsistent==0.15.6 \
            --hash=sha256:f3b280d030afb652f79d67c5586157c5c1355c9a58dfc7940566e28d28f3df1b
        QDarkStyle==2.6.8 \
   DIR diff --git a/contrib/requirements/requirements-binaries.txt b/contrib/requirements/requirements-binaries.txt
       t@@ -1,2 +1,3 @@
        PyQt5<5.12
        PyQt5-sip<=4.19.13
       +pycryptodomex>=3.7
   DIR diff --git a/contrib/requirements/requirements.txt b/contrib/requirements/requirements.txt
       t@@ -9,7 +9,6 @@ aiohttp>=3.3.0,<4.0.0
        aiohttp_socks
        certifi
        bitstring
       -pycryptodomex>=3.7
        jsonrpcserver
        jsonrpcclient
        attrs
   DIR diff --git a/electrum/crypto.py b/electrum/crypto.py
       t@@ -25,6 +25,7 @@
        
        import base64
        import os
       +import sys
        import hashlib
        import hmac
        from typing import Union
       t@@ -35,10 +36,33 @@ from .util import assert_bytes, InvalidPassword, to_bytes, to_string, WalletFile
        from .i18n import _
        
        
       +HAS_CRYPTODOME = False
        try:
       -    from Cryptodome.Cipher import AES
       +    from Cryptodome.Cipher import ChaCha20_Poly1305 as CD_ChaCha20_Poly1305
       +    from Cryptodome.Cipher import ChaCha20 as CD_ChaCha20
       +    from Cryptodome.Cipher import AES as CD_AES
        except:
       -    AES = None
       +    pass
       +else:
       +    HAS_CRYPTODOME = True
       +
       +HAS_CRYPTOGRAPHY = False
       +try:
       +    import cryptography
       +    from cryptography import exceptions
       +    from cryptography.hazmat.primitives.ciphers import Cipher as CG_Cipher
       +    from cryptography.hazmat.primitives.ciphers import algorithms as CG_algorithms
       +    from cryptography.hazmat.primitives.ciphers import modes as CG_modes
       +    from cryptography.hazmat.backends import default_backend as CG_default_backend
       +    import cryptography.hazmat.primitives.ciphers.aead as CG_aead
       +except:
       +    pass
       +else:
       +    HAS_CRYPTOGRAPHY = True
       +
       +
       +if not (HAS_CRYPTODOME or HAS_CRYPTOGRAPHY):
       +    sys.exit(f"Error: at least one of ('pycryptodomex', 'cryptography') needs to be installed.")
        
        
        class InvalidPadding(Exception):
       t@@ -67,8 +91,12 @@ def strip_PKCS7_padding(data: bytes) -> bytes:
        def aes_encrypt_with_iv(key: bytes, iv: bytes, data: bytes) -> bytes:
            assert_bytes(key, iv, data)
            data = append_PKCS7_padding(data)
       -    if AES:
       -        e = AES.new(key, AES.MODE_CBC, iv).encrypt(data)
       +    if HAS_CRYPTODOME:
       +        e = CD_AES.new(key, CD_AES.MODE_CBC, iv).encrypt(data)
       +    elif HAS_CRYPTOGRAPHY:
       +        cipher = CG_Cipher(CG_algorithms.AES(key), CG_modes.CBC(iv), backend=CG_default_backend())
       +        encryptor = cipher.encryptor()
       +        e = encryptor.update(data) + encryptor.finalize()
            else:
                aes_cbc = pyaes.AESModeOfOperationCBC(key, iv=iv)
                aes = pyaes.Encrypter(aes_cbc, padding=pyaes.PADDING_NONE)
       t@@ -78,9 +106,13 @@ def aes_encrypt_with_iv(key: bytes, iv: bytes, data: bytes) -> bytes:
        
        def aes_decrypt_with_iv(key: bytes, iv: bytes, data: bytes) -> bytes:
            assert_bytes(key, iv, data)
       -    if AES:
       -        cipher = AES.new(key, AES.MODE_CBC, iv)
       +    if HAS_CRYPTODOME:
       +        cipher = CD_AES.new(key, CD_AES.MODE_CBC, iv)
                data = cipher.decrypt(data)
       +    elif HAS_CRYPTOGRAPHY:
       +        cipher = CG_Cipher(CG_algorithms.AES(key), CG_modes.CBC(iv), backend=CG_default_backend())
       +        decryptor = cipher.decryptor()
       +        data = decryptor.update(data) + decryptor.finalize()
            else:
                aes_cbc = pyaes.AESModeOfOperationCBC(key, iv=iv)
                aes = pyaes.Decrypter(aes_cbc, padding=pyaes.PADDING_NONE)
       t@@ -216,3 +248,55 @@ def hmac_oneshot(key: bytes, msg: bytes, digest) -> bytes:
                return hmac.digest(key, msg, digest)
            else:
                return hmac.new(key, msg, digest).digest()
       +
       +
       +def chacha20_poly1305_encrypt(*, key: bytes, nonce: bytes, associated_data: bytes, data: bytes) -> bytes:
       +    assert isinstance(key, (bytes, bytearray))
       +    assert isinstance(nonce, (bytes, bytearray))
       +    assert isinstance(associated_data, (bytes, bytearray))
       +    assert isinstance(data, (bytes, bytearray))
       +    if HAS_CRYPTODOME:
       +        cipher = CD_ChaCha20_Poly1305.new(key=key, nonce=nonce)
       +        cipher.update(associated_data)
       +        ciphertext, mac = cipher.encrypt_and_digest(plaintext=data)
       +        return ciphertext + mac
       +    if HAS_CRYPTOGRAPHY:
       +        a = CG_aead.ChaCha20Poly1305(key)
       +        return a.encrypt(nonce, data, associated_data)
       +    raise Exception("no chacha20 backed found")
       +
       +
       +def chacha20_poly1305_decrypt(*, key: bytes, nonce: bytes, associated_data: bytes, data: bytes) -> bytes:
       +    assert isinstance(key, (bytes, bytearray))
       +    assert isinstance(nonce, (bytes, bytearray))
       +    assert isinstance(associated_data, (bytes, bytearray))
       +    assert isinstance(data, (bytes, bytearray))
       +    if HAS_CRYPTODOME:
       +        cipher = CD_ChaCha20_Poly1305.new(key=key, nonce=nonce)
       +        cipher.update(associated_data)
       +        # raises ValueError if not valid (e.g. incorrect MAC)
       +        return cipher.decrypt_and_verify(ciphertext=data[:-16], received_mac_tag=data[-16:])
       +    if HAS_CRYPTOGRAPHY:
       +        a = CG_aead.ChaCha20Poly1305(key)
       +        try:
       +            return a.decrypt(nonce, data, associated_data)
       +        except cryptography.exceptions.InvalidTag as e:
       +            raise ValueError("invalid tag") from e
       +    raise Exception("no chacha20 backed found")
       +
       +
       +def chacha20_encrypt(*, key: bytes, nonce: bytes, data: bytes) -> bytes:
       +    assert isinstance(key, (bytes, bytearray))
       +    assert isinstance(nonce, (bytes, bytearray))
       +    assert isinstance(data, (bytes, bytearray))
       +    assert len(nonce) == 8, f"unexpected nonce size: {len(nonce)} (expected: 8)"
       +    if HAS_CRYPTODOME:
       +        cipher = CD_ChaCha20.new(key=key, nonce=nonce)
       +        return cipher.encrypt(data)
       +    if HAS_CRYPTOGRAPHY:
       +        nonce = bytes(8) + nonce  # cryptography wants 16 byte nonces
       +        algo = CG_algorithms.ChaCha20(key=key, nonce=nonce)
       +        cipher = CG_Cipher(algo, mode=None, backend=CG_default_backend())
       +        encryptor = cipher.encryptor()
       +        return encryptor.update(data)
       +    raise Exception("no chacha20 backed found")
   DIR diff --git a/electrum/lnonion.py b/electrum/lnonion.py
       t@@ -27,10 +27,8 @@ import hashlib
        from typing import Sequence, List, Tuple, NamedTuple, TYPE_CHECKING
        from enum import IntEnum, IntFlag
        
       -from Cryptodome.Cipher import ChaCha20
       -
        from . import ecc
       -from .crypto import sha256, hmac_oneshot
       +from .crypto import sha256, hmac_oneshot, chacha20_encrypt
        from .util import bh2u, profiler, xor_bytes, bfh
        from .lnutil import (get_ecdh, PaymentFailure, NUM_MAX_HOPS_IN_PAYMENT_PATH,
                             NUM_MAX_EDGES_IN_PAYMENT_PATH, ShortChannelID)
       t@@ -227,8 +225,9 @@ def generate_filler(key_type: bytes, num_hops: int, hop_size: int,
        
        
        def generate_cipher_stream(stream_key: bytes, num_bytes: int) -> bytes:
       -    cipher = ChaCha20.new(key=stream_key, nonce=bytes(8))
       -    return cipher.encrypt(bytes(num_bytes))
       +    return chacha20_encrypt(key=stream_key,
       +                            nonce=bytes(8),
       +                            data=bytes(num_bytes))
        
        
        class ProcessedOnionPacket(NamedTuple):
   DIR diff --git a/electrum/lntransport.py b/electrum/lntransport.py
       t@@ -9,9 +9,7 @@ import hashlib
        import asyncio
        from asyncio import StreamReader, StreamWriter
        
       -from Cryptodome.Cipher import ChaCha20_Poly1305
       -
       -from .crypto import sha256, hmac_oneshot
       +from .crypto import sha256, hmac_oneshot, chacha20_poly1305_encrypt, chacha20_poly1305_decrypt
        from .lnutil import (get_ecdh, privkey_to_pubkey, LightningPeerConnectionClosed,
                             HandshakeFailed, LNPeerAddr)
        from . import ecc
       t@@ -41,17 +39,17 @@ def get_nonce_bytes(n):
        
        def aead_encrypt(key: bytes, nonce: int, associated_data: bytes, data: bytes) -> bytes:
            nonce_bytes = get_nonce_bytes(nonce)
       -    cipher = ChaCha20_Poly1305.new(key=key, nonce=nonce_bytes)
       -    cipher.update(associated_data)
       -    ciphertext, mac = cipher.encrypt_and_digest(plaintext=data)
       -    return ciphertext + mac
       +    return chacha20_poly1305_encrypt(key=key,
       +                                     nonce=nonce_bytes,
       +                                     associated_data=associated_data,
       +                                     data=data)
        
        def aead_decrypt(key: bytes, nonce: int, associated_data: bytes, data: bytes) -> bytes:
            nonce_bytes = get_nonce_bytes(nonce)
       -    cipher = ChaCha20_Poly1305.new(key=key, nonce=nonce_bytes)
       -    cipher.update(associated_data)
       -    # raises ValueError if not valid (e.g. incorrect MAC)
       -    return cipher.decrypt_and_verify(ciphertext=data[:-16], received_mac_tag=data[-16:])
       +    return chacha20_poly1305_decrypt(key=key,
       +                                     nonce=nonce_bytes,
       +                                     associated_data=associated_data,
       +                                     data=data)
        
        def get_bolt8_hkdf(salt, ikm):
            """RFC5869 HKDF instantiated in the specific form
   DIR diff --git a/electrum/tests/test_bitcoin.py b/electrum/tests/test_bitcoin.py
       t@@ -34,8 +34,8 @@ except ImportError:
        
        
        def needs_test_with_all_aes_implementations(func):
       -    """Function decorator to run a unit test twice:
       -    once when pycryptodomex is not available, once when it is.
       +    """Function decorator to run a unit test multiple times:
       +    once with each AES implementation.
        
            NOTE: this is inherently sequential;
            tests running in parallel would break things
       t@@ -44,18 +44,46 @@ def needs_test_with_all_aes_implementations(func):
                if FAST_TESTS:  # if set, only run tests once, using fastest implementation
                    func(*args, **kwargs)
                    return
       -        _aes = crypto.AES
       -        crypto.AES = None
       +        has_cryptodome = crypto.HAS_CRYPTODOME
       +        has_cryptography = crypto.HAS_CRYPTOGRAPHY
                try:
       -            # first test without pycryptodomex
       -            func(*args, **kwargs)
       +            (crypto.HAS_CRYPTODOME, crypto.HAS_CRYPTOGRAPHY) = False, False
       +            func(*args, **kwargs)  # pyaes
       +            if has_cryptodome:
       +                (crypto.HAS_CRYPTODOME, crypto.HAS_CRYPTOGRAPHY) = True, False
       +                func(*args, **kwargs)  # cryptodome
       +            if has_cryptography:
       +                (crypto.HAS_CRYPTODOME, crypto.HAS_CRYPTOGRAPHY) = False, True
       +                func(*args, **kwargs)  # cryptography
                finally:
       -            crypto.AES = _aes
       -        # if pycryptodomex is not available, we are done
       -        if not _aes:
       +            crypto.HAS_CRYPTODOME = has_cryptodome
       +            crypto.HAS_CRYPTOGRAPHY = has_cryptography
       +    return run_test
       +
       +
       +def needs_test_with_all_chacha20_implementations(func):
       +    """Function decorator to run a unit test multiple times:
       +    once with each ChaCha20/Poly1305 implementation.
       +
       +    NOTE: this is inherently sequential;
       +    tests running in parallel would break things
       +    """
       +    def run_test(*args, **kwargs):
       +        if FAST_TESTS:  # if set, only run tests once, using fastest implementation
       +            func(*args, **kwargs)
                    return
       -        # if pycryptodomex is available, test again now
       -        func(*args, **kwargs)
       +        has_cryptodome = crypto.HAS_CRYPTODOME
       +        has_cryptography = crypto.HAS_CRYPTOGRAPHY
       +        try:
       +            if has_cryptodome:
       +                (crypto.HAS_CRYPTODOME, crypto.HAS_CRYPTOGRAPHY) = True, False
       +                func(*args, **kwargs)  # cryptodome
       +            if has_cryptography:
       +                (crypto.HAS_CRYPTODOME, crypto.HAS_CRYPTOGRAPHY) = False, True
       +                func(*args, **kwargs)  # cryptography
       +        finally:
       +            crypto.HAS_CRYPTODOME = has_cryptodome
       +            crypto.HAS_CRYPTOGRAPHY = has_cryptography
            return run_test
        
        
       t@@ -67,7 +95,11 @@ class Test_bitcoin(ElectrumTestCase):
        
            def test_pycryptodomex_is_available(self):
                # we want the unit testing framework to test with pycryptodomex available.
       -        self.assertTrue(bool(crypto.AES))
       +        self.assertTrue(bool(crypto.HAS_CRYPTODOME))
       +
       +    def test_cryptography_is_available(self):
       +        # we want the unit testing framework to test with cryptography available.
       +        self.assertTrue(bool(crypto.HAS_CRYPTOGRAPHY))
        
            @needs_test_with_all_aes_implementations
            def test_crypto(self):
       t@@ -223,6 +255,34 @@ class Test_bitcoin(ElectrumTestCase):
                    with self.assertRaises(InvalidPassword):
                        crypto.pw_decode(enc, wrong_password, version=version)
        
       +    @needs_test_with_all_chacha20_implementations
       +    def test_chacha20_poly1305_encrypt(self):
       +        key = bytes.fromhex('37326d9d69a83b815ddfd947d21b0dd39111e5b6a5a44042c44d570ea03e3179')
       +        nonce = bytes.fromhex('010203040506070809101112')
       +        associated_data = bytes.fromhex('30c9572d4305d4f3ccb766b1db884da6f1e0086f55136a39740700c272095717')
       +        data = bytes.fromhex('4a6cd75da76cedf0a8a47e3a5734a328')
       +        self.assertEqual(bytes.fromhex('90fb51fcde1fbe4013500bd7a32280445d80ee21f0aa3acd30df72cf609de064'),
       +                         crypto.chacha20_poly1305_encrypt(key=key, nonce=nonce, associated_data=associated_data, data=data))
       +
       +    @needs_test_with_all_chacha20_implementations
       +    def test_chacha20_poly1305_decrypt(self):
       +        key = bytes.fromhex('37326d9d69a83b815ddfd947d21b0dd39111e5b6a5a44042c44d570ea03e3179')
       +        nonce = bytes.fromhex('010203040506070809101112')
       +        associated_data = bytes.fromhex('30c9572d4305d4f3ccb766b1db884da6f1e0086f55136a39740700c272095717')
       +        data = bytes.fromhex('90fb51fcde1fbe4013500bd7a32280445d80ee21f0aa3acd30df72cf609de064')
       +        self.assertEqual(bytes.fromhex('4a6cd75da76cedf0a8a47e3a5734a328'),
       +                         crypto.chacha20_poly1305_decrypt(key=key, nonce=nonce, associated_data=associated_data, data=data))
       +        with self.assertRaises(ValueError):
       +            crypto.chacha20_poly1305_decrypt(key=key, nonce=nonce, associated_data=b'', data=data)
       +
       +    @needs_test_with_all_chacha20_implementations
       +    def test_chacha20_encrypt(self):
       +        key = bytes.fromhex('37326d9d69a83b815ddfd947d21b0dd39111e5b6a5a44042c44d570ea03e3179')
       +        nonce = bytes.fromhex('0102030405060708')
       +        data = bytes.fromhex('38a0e0a7c865fe9ca31f0730cfcab610f18e6da88dc3790f1d243f711a257c78')
       +        self.assertEqual(bytes.fromhex('f62fbd74d197323c7c3d5658476a884d38ee6f4b5500add1e8dc80dcd9c15dff'),
       +                         crypto.chacha20_encrypt(key=key, nonce=nonce, data=data))
       +
            def test_sha256d(self):
                self.assertEqual(b'\x95MZI\xfdp\xd9\xb8\xbc\xdb5\xd2R&x)\x95\x7f~\xf7\xfalt\xf8\x84\x19\xbd\xc5\xe8"\t\xf4',
                                 sha256d(u"test"))
   DIR diff --git a/electrum/tests/test_lnpeer.py b/electrum/tests/test_lnpeer.py
       t@@ -28,6 +28,7 @@ from electrum.logging import console_stderr_handler
        from electrum.lnworker import PaymentInfo, RECEIVED, PR_UNPAID
        
        from .test_lnchannel import create_test_channels
       +from .test_bitcoin import needs_test_with_all_chacha20_implementations
        from . import ElectrumTestCase
        
        def keypair():
       t@@ -246,6 +247,7 @@ class TestPeer(ElectrumTestCase):
                with self.assertRaises(concurrent.futures.CancelledError):
                    run(f())
        
       +    @needs_test_with_all_chacha20_implementations
            def test_reestablish_with_old_state(self):
                alice_channel, bob_channel = create_test_channels()
                alice_channel_0, bob_channel_0 = create_test_channels() # these are identical
       t@@ -279,6 +281,7 @@ class TestPeer(ElectrumTestCase):
                with self.assertRaises(concurrent.futures.CancelledError):
                    run(f())
        
       +    @needs_test_with_all_chacha20_implementations
            def test_payment(self):
                alice_channel, bob_channel = create_test_channels()
                p1, p2, w1, w2, _q1, _q2 = self.prepare_peers(alice_channel, bob_channel)
       t@@ -293,6 +296,7 @@ class TestPeer(ElectrumTestCase):
                with self.assertRaises(concurrent.futures.CancelledError):
                    run(f())
        
       +    @needs_test_with_all_chacha20_implementations
            def test_close(self):
                alice_channel, bob_channel = create_test_channels()
                p1, p2, w1, w2, _q1, _q2 = self.prepare_peers(alice_channel, bob_channel)
   DIR diff --git a/electrum/tests/test_lnrouter.py b/electrum/tests/test_lnrouter.py
       t@@ -12,6 +12,8 @@ from electrum.constants import BitcoinTestnet
        from electrum.simple_config import SimpleConfig
        
        from . import TestCaseForTestnet
       +from .test_bitcoin import needs_test_with_all_chacha20_implementations
       +
        
        class Test_LNRouter(TestCaseForTestnet):
        
       t@@ -108,6 +110,7 @@ class Test_LNRouter(TestCaseForTestnet):
                self._loop_thread.join(timeout=1)
                cdb.sql_thread.join(timeout=1)
        
       +    @needs_test_with_all_chacha20_implementations
            def test_new_onion_packet(self):
                # test vector from bolt-04
                payment_path_pubkeys = [
       t@@ -140,6 +143,7 @@ class Test_LNRouter(TestCaseForTestnet):
parazyd.org:70 /git/electrum/commit/dbd77b7d8e5b4542042bc8a262435f45d77706c0.gph:580: line too long