Skip to content

Commit

Permalink
Chore: embit Upgrade to 0.4.13 (ripemd160 replacement) (#1702)
Browse files Browse the repository at this point in the history
* Removed dependency of openssl.ripemd160  and replaced it by embit dependency.
This is useful because embit handles the missing ripemd160 in openssl3: diybitcoinhardware/embit#28 starting from version 0.4.13

* Update src/cryptoadvance/specter/devices/specter.py

Co-authored-by: Stepan Snigirev <[email protected]>

* dependencies and tests

* fix tests

* rollback unintended changes

Co-authored-by: Stepan Snigirev <[email protected]>
Co-authored-by: Kim Neunert <[email protected]>
  • Loading branch information
3 people authored May 5, 2022
1 parent 0f53066 commit b5bf6f1
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 50 deletions.
2 changes: 1 addition & 1 deletion requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ requests==2.26.0
pysocks==1.7.1
six==1.16.0
stem==1.8.0
embit==0.4.12
embit==0.4.13
psutil==5.7.3
pyopenssl==20.0.1
flask_wtf==0.15.1
Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ ecdsa==0.17.0 \
# via
# bitbox02
# hwi
embit==0.4.12 \
--hash=sha256:d340107dc1604581df59f844d4eb76ec34b0219c2ac2cbc1837c14938a4730ee
embit==0.4.13 \
--hash=sha256:46a5ac26f7e9132a7f60e2b1a0938f7104f841e98e23476f395b7df4f1fb7552
# via -r requirements.in
flask-apscheduler==1.12.3 \
--hash=sha256:d60948d1f2be9eb4772f68c3308ba3f973755219d13947266f89292ad6df63fc
Expand Down
4 changes: 2 additions & 2 deletions src/cryptoadvance/specter/devices/specter.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from embit.liquid.transaction import LSIGHASH
from binascii import a2b_base64, b2a_base64
from embit.liquid.networks import get_network
from embit.hashes import hash160
import logging

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -173,6 +174,5 @@ def get_wallet_fingerprint(wallet):
Unique fingerprint of the wallet -
first 4 bytes of hash160 of its descriptor
"""
h256 = hashlib.sha256(get_wallet_qr_descriptor(wallet).encode()).digest()
h160 = hashlib.new("ripemd160", h256).digest()
h160 = hash160(get_wallet_qr_descriptor(wallet).encode())
return h160[:4]
6 changes: 1 addition & 5 deletions src/cryptoadvance/specter/util/xpub.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import hashlib
from embit import base58


def hash160(d):
# ripemd160(sha256(d))
return hashlib.new("ripemd160", hashlib.sha256(d).digest()).digest()
from embit.hashes import hash160


def convert_xpub_prefix(xpub, prefix_bytes):
Expand Down
7 changes: 6 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@

logger = logging.getLogger(__name__)

pytest_plugins = ["fix_ghost_machine", "fix_devices_and_wallets", "fix_testnet"]
pytest_plugins = [
"fix_ghost_machine",
"fix_keys_and_seeds",
"fix_devices_and_wallets",
"fix_testnet",
]

# This is from https://stackoverflow.com/questions/132058/showing-the-stack-trace-from-a-running-python-application
# it enables stopping a hanging test via sending the pytest-process a SIGUSR2 (12)
Expand Down
8 changes: 0 additions & 8 deletions tests/fix_devices_and_wallets.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,6 @@
from cryptoadvance.specter.wallet import Wallet, Device


mnemonic_ghost = (
"ghost ghost ghost ghost ghost ghost ghost ghost ghost ghost ghost machine"
)
mnemonic_zoo = (
"zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when"
)


def create_hot_wallet_device(
specter_regtest_configured, name=None, mnemonic=None
) -> Device:
Expand Down
138 changes: 138 additions & 0 deletions tests/fix_keys_and_seeds.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
from binascii import hexlify
import json
import pytest
from cryptoadvance.specter.key import Key

from embit.bip39 import mnemonic_to_seed
from embit.bip32 import HDKey, NETWORKS
from embit import script

mnemonic_ghost_machine = (
"ghost ghost ghost ghost ghost ghost ghost ghost ghost ghost ghost machine"
)

mnemonic_zoo_when = (
"zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when"
)


@pytest.fixture
def mnemonic_keen_join():
return 11 * "keen " + "join"


# hold hold hold hold hold hold hold hold hold hold hold accident
# This is a formal creation of all major bitcoin artifacts from the
# hold accident mnemonic


@pytest.fixture
def mnemonic_hold_accident():
return 11 * "hold " + "accident"


@pytest.fixture
def seed_hold_accident(mnemonic_hold_accident):
seed = mnemonic_to_seed(mnemonic_hold_accident)
print(f"Hold Accident seed: {hexlify(seed)}")
return mnemonic_to_seed(mnemonic_hold_accident)


@pytest.fixture
def rootkey_hold_accident(seed_hold_accident):
rootkey = HDKey.from_seed(seed_hold_accident)
print(f"Hold Accident rootkey: {rootkey.to_base58()}")
# xprv9s21ZrQH143K45uYUg7zhHku3bik5a2nw8XcanYCUGHn7RE1Bhkr53RWcjAQVFDTmruDceNDAGbc7yYsZCGveKMDrPr18hMsMcvYTGJ4Mae
print(f"Hold Accident rootkey fp: {hexlify(rootkey.my_fingerprint)}")
return rootkey


@pytest.fixture
def acc0xprv_hold_accident(rootkey_hold_accident: HDKey):
xprv = rootkey_hold_accident.derive("m/84h/1h/0h")
print(f"Hold Accident acc0xprv: {xprv.to_base58(version=NETWORKS['test']['xprv'])}")
# tprv8g6WHqYgjvGrEU6eEdJxXzNUqN8DvLFb3iv3yUVomNRcNqT5JSKpTVNBzBD3qTDmmhRHPLcjE5fxFcGmU3FqU5u9zHm9W6sGX2isPMZAKq2

return xprv


@pytest.fixture
def acc0xpub_hold_accident(acc0xprv_hold_accident: HDKey):
xpub = acc0xprv_hold_accident.to_public()
print(f"Hold Accident acc0xpub: {xpub.to_base58(version=NETWORKS['test']['xpub'])}")
# vpub5YkPJgRQsev79YZM1NRDKJWDjLFcD2xSFAt6LehC5iiMMqQgMHyCFQzwsu16Rx9rBpXZVXPjWAxybuCpsayaw8qCDZtjwH9vifJ7WiQkHwu
return xpub


@pytest.fixture
def acc0key0pubkey_hold_accident(acc0xpub_hold_accident: HDKey):
pubkey = acc0xpub_hold_accident.derive("m/0/0")
print("------------")
print(pubkey.key)
# 03584dc8282f626ce5570633018be0760baae68f1ecd6e801192c466ada55f5f31
print(hexlify(pubkey.sec()))
# b'03584dc8282f626ce5570633018be0760baae68f1ecd6e801192c466ada55f5f31'
return pubkey


@pytest.fixture
def acc0key0addr_hold_accident(acc0key0pubkey_hold_accident):
sc = script.p2wpkh(acc0key0pubkey_hold_accident)
address = sc.address(NETWORKS["test"])
print(address) # m/84'/1'/0'/0/0
# tb1qnwc84tkupy5v0tzgt27zkd3uxex3nmyr6vfhdd
return address


@pytest.fixture
def key_hold_accident(acc0key0pubkey_hold_accident):
sc = script.p2wpkh(acc0key0pubkey_hold_accident)
address = sc.address(NETWORKS["test"])
print(address) # m/84'/1'/0'/0/0
# tb1qnwc84tkupy5v0tzgt27zkd3uxex3nmyr6vfhdd
return address


@pytest.fixture
def acc0key_hold_accident(acc0xpub_hold_accident, rootkey_hold_accident: HDKey):

key: Key = Key(
acc0xpub_hold_accident.to_base58(
version=NETWORKS["test"]["xpub"]
), # original (ToDo: better original)
hexlify(rootkey_hold_accident.my_fingerprint).decode("utf-8"), # fingerprint
"m/84h/1h/0h", # derivation
"wpkh", # key_type
"Muuh", # purpose
acc0xpub_hold_accident.to_base58(version=NETWORKS["test"]["xpub"]), # xpub
)
mydict = key.json
print(json.dumps(mydict))

return key


# random other keys


@pytest.fixture
def a_key():
a_key = Key(
"Vpub5n9kKePTPPGtw3RddeJWJe29epEyBBcoHbbPi5HhpoG2kTVsSCUzsad33RJUt3LktEUUPPofcZczuudnwR7ZgkAkT6N2K2Z7wdyjYrVAkXM",
"08686ac6",
"m/48h/1h/0h/2h",
"wsh",
"",
"tpubDFHpKypXq4kwUrqLotPs6fCic5bFqTRGMBaTi9s5YwwGymE8FLGwB2kDXALxqvNwFxB1dLWYBmmeFVjmUSdt2AsaQuPmkyPLBKRZW8BGCiL",
)
return a_key


@pytest.fixture
def a_tpub_only_key():
a_tpub_only_key = Key.from_json(
{
"original": "tpubDDZ5jjGT5RvrAyjoLZfdCfv1PAPmicnhNctwZGKiCMF1Zy5hCGMqppxwYZzWgvPqk7LucMMHo7rkB6Dyj5ZLd2W62FAEP3U6pV4jD5gb9ma"
}
)
return a_tpub_only_key
37 changes: 37 additions & 0 deletions tests/test_devices_specter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from pathlib import Path

from cryptoadvance.specter.devices.specter import (
Specter,
get_wallet_fingerprint,
get_wallet_qr_descriptor,
)
from cryptoadvance.specter.key import Key
from cryptoadvance.specter.managers.device_manager import DeviceManager
from cryptoadvance.specter.managers.wallet_manager import WalletManager
from cryptoadvance.specter.wallet import Wallet
from mock import MagicMock
import logging


def test_get_wallet_qr_descriptor(
caplog, specter_regtest_configured, acc0key_hold_accident: Key
):
# logging.getLogger("cryptoadvance.specter.rpc").setLevel(logging.DEBUG)
wallet_manager: WalletManager = specter_regtest_configured.wallet_manager
specter_device = specter_regtest_configured.device_manager.add_device(
"specter_device", "specter", [acc0key_hold_accident]
)
assert isinstance(specter_device, Specter)
print(specter_device.keys[0].json)
specter_device.keys
wallet: Wallet = wallet_manager.create_wallet(
"someName", 1, "wpkh", [specter_device.keys[0]], [specter_device]
)

assert wallet != None

assert (
get_wallet_qr_descriptor(wallet)
== "wpkh([ccf2e5c3/84h/1h/0h]tpubDCnYSFavtHxX7w8S8GyYwQ2bQPeA5fSVd2WqFzY7BeE1DKhqvq9Qdyz4AM13xGQvo1J5c46ixbW84evZhqerR3eh1r2tndT8r51p3sxiQ8F)"
)
assert get_wallet_fingerprint(wallet) == b"Kh\xb5*"
23 changes: 9 additions & 14 deletions tests/test_managers_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from cryptoadvance.specter.managers.wallet_manager import WalletManager


def test_DeviceManager(empty_data_folder):
def test_DeviceManager(empty_data_folder, a_key, a_tpub_only_key):
# A DeviceManager manages devices, specifically the persistence
# of them via json-files in an empty data folder
dm = DeviceManager(data_folder=empty_data_folder)
Expand All @@ -22,13 +22,8 @@ def test_DeviceManager(empty_data_folder):
"tpubDFHpKypXq4kwUrqLotPs6fCic5bFqTRGMBaTi9s5YwwGymE8FLGwB2kDXALxqvNwFxB1dLWYBmmeFVjmUSdt2AsaQuPmkyPLBKRZW8BGCiL",
)
# the DeviceManager doesn't care so much about the content of a key
# so this is a minimal valid "key":
another_key = Key.from_json(
{
"original": "tpubDDZ5jjGT5RvrAyjoLZfdCfv1PAPmicnhNctwZGKiCMF1Zy5hCGMqppxwYZzWgvPqk7LucMMHo7rkB6Dyj5ZLd2W62FAEP3U6pV4jD5gb9ma"
}
)
dm.add_device("some_name", "the_type", [a_key, another_key])
# so let's add two keys:
dm.add_device("some_name", "the_type", [a_key, a_tpub_only_key])
# A json file was generated for the new device:
assert os.path.isfile(dm.devices["some_name"].fullpath)
# You can access the new device either by its name of with `get_by_alias` by its alias
Expand All @@ -46,7 +41,7 @@ def test_DeviceManager(empty_data_folder):

# The DeviceManager also has a `devices_names` property, returning a sorted list of the names of all devices
assert dm.devices_names == ["some_name"]
dm.add_device("another_name", "the_type", [a_key, another_key])
dm.add_device("another_name", "the_type", [a_key, a_tpub_only_key])
assert dm.devices_names == ["another_name", "some_name"]

# You can also remove a device - which will delete its json and remove it from the manager
Expand All @@ -70,7 +65,7 @@ def test_DeviceManager(empty_data_folder):
assert some_device.device_type == "other"
assert len(some_device.keys) == 2
assert some_device.keys[0] == a_key
assert some_device.keys[1] == another_key
assert some_device.keys[1] == a_tpub_only_key

# Keys can be added and removed. It will instantly update the underlying json
# Adding keys can be done by passing an array of keys object to the `add_keys` method of a device
Expand All @@ -83,27 +78,27 @@ def test_DeviceManager(empty_data_folder):
some_device.add_keys([third_key])
assert len(some_device.keys) == 3
assert some_device.keys[0] == a_key
assert some_device.keys[1] == another_key
assert some_device.keys[1] == a_tpub_only_key
assert some_device.keys[2] == third_key

# adding an existing key will do nothing
some_device.add_keys([third_key])
assert len(some_device.keys) == 3
assert some_device.keys[0] == a_key
assert some_device.keys[1] == another_key
assert some_device.keys[1] == a_tpub_only_key
assert some_device.keys[2] == third_key

# Removing a key can be done by passing the `original` property of the key to remove to the `remove_key` method of a device
some_device.remove_key(third_key)
assert len(some_device.keys) == 2
assert some_device.keys[0] == a_key
assert some_device.keys[1] == another_key
assert some_device.keys[1] == a_tpub_only_key

# removing a none existing key will do nothing
some_device.remove_key(third_key)
assert len(some_device.keys) == 2
assert some_device.keys[0] == a_key
assert some_device.keys[1] == another_key
assert some_device.keys[1] == a_tpub_only_key


def test_device_wallets(
Expand Down
19 changes: 2 additions & 17 deletions tests/test_persistence.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,24 +57,9 @@ def test_write_wallet(app, monkeypatch, caplog):
assert "callback failed" in caplog.text


def test_write_device(app):
a_key = Key(
"Vpub5n9kKePTPPGtw3RddeJWJe29epEyBBcoHbbPi5HhpoG2kTVsSCUzsad33RJUt3LktEUUPPofcZczuudnwR7ZgkAkT6N2K2Z7wdyjYrVAkXM",
"08686ac6",
"m/48h/1h/0h/2h",
"wsh",
"",
"tpubDFHpKypXq4kwUrqLotPs6fCic5bFqTRGMBaTi9s5YwwGymE8FLGwB2kDXALxqvNwFxB1dLWYBmmeFVjmUSdt2AsaQuPmkyPLBKRZW8BGCiL",
)
# the DeviceManager doesn't care so much about the content of a key
# so this is a minimal valid "key":
another_key = Key.from_json(
{
"original": "tpubDDZ5jjGT5RvrAyjoLZfdCfv1PAPmicnhNctwZGKiCMF1Zy5hCGMqppxwYZzWgvPqk7LucMMHo7rkB6Dyj5ZLd2W62FAEP3U6pV4jD5gb9ma"
}
)
def test_write_device(app, a_key, a_tpub_only_key):
app.specter.device_manager.add_device(
"some_name2", "the_type", [a_key, another_key]
"some_name2", "the_type", [a_key, a_tpub_only_key]
)
write_device(
app.specter.device_manager.get_by_alias("some_name2"),
Expand Down

0 comments on commit b5bf6f1

Please sign in to comment.