Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Add derive to SoftwareSigner #7

Merged
merged 5 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 18 additions & 16 deletions bitcoin_usb/seed_tools.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import logging
from typing import Tuple
from typing import Optional, Tuple

import bdkpython as bdk
from bitcointx import select_chain_params
Expand All @@ -11,17 +11,19 @@
from .address_types import SimplePubKeyProvider


def key_origin_fits_network(key_origin: str, network: bdk.Network):
network_str = key_origin.split("/")[2]
assert network_str.endswith("h")
network_index = int(network_str.replace("h", ""))
def get_network_index(key_origin: str) -> Optional[int]:
splitted = key_origin.split("/")
if len(splitted) < 3:
logger.warning(f"{key_origin} has too few levels for a network_index")
return None

if network_index == 0:
return network == bdk.Network.BITCOIN
elif network_index == 1:
return network != bdk.Network.BITCOIN
else:
raise ValueError(f"Unknown network/coin type {network_str} in {key_origin}")
network_str = splitted[2]
if not network_str.endswith("h"):
logger.warning(f"The network index ({network_str}) must be hardened")
return None

network_index = int(network_str.replace("h", ""))
return network_index


def get_mnemonic_seed(mnemonic: str):
Expand All @@ -47,8 +49,6 @@ def derive(mnemonic: str, key_origin: str, network: bdk.Network) -> Tuple[str, s
Returns:
Tuple[str, str]: xpub, fingerprint (where fingerprint is the master fingerprint)
"""
if not key_origin_fits_network(key_origin, network):
raise ValueError(f"{key_origin} does not fit the selected network {network}")

# Select network parameters
network_params = {
Expand All @@ -64,11 +64,13 @@ def derive(mnemonic: str, key_origin: str, network: bdk.Network) -> Tuple[str, s
# Create a master extended key from the seed
master_key = CCoinExtKey.from_seed(seed_bytes)

# Derive the xpub at the specified origin
derived_key = master_key.derive_path(key_origin)
if key_origin == "m":
derived_key = master_key
else:
# Derive the xpub at the specified origin
derived_key = master_key.derive_path(key_origin)

# Extract xpub

xpub = str(derived_key.neuter())

# Get the fingerprint
Expand Down
4 changes: 4 additions & 0 deletions bitcoin_usb/software_signer.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ def __init__(self, mnemonic: str, network: bdk.Network) -> None:
super().__init__(network=network)
self.mnemonic = mnemonic

def derive(self, key_origin: str):
xpub, fingerprint = derive(self.mnemonic, key_origin, self.network)
return xpub

def get_fingerprint(self) -> str:
# it doesn't mattrer which AddressTypes i choose, because the fingerprint is identical for all
address_type = AddressTypes.p2wsh
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ disable_error_code = "assignment"

[tool.poetry]
name = "bitcoin-usb"
version = "0.2.1"
version = "0.2.2"
authors = ["andreasgriffin <[email protected]>"]
license = "GPL-3.0"
readme = "README.md"
Expand Down
73 changes: 60 additions & 13 deletions tests/test_derivation.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import pytest

from bitcoin_usb.address_types import (
AddressTypes,
DescriptorInfo,
bdk,
get_all_address_types,
logging,
)
from bitcoin_usb.seed_tools import derive, derive_spk_provider
from bitcoin_usb.seed_tools import derive_spk_provider, get_network_index

# test seeds
# seed1: spider manual inform reject arch raccoon betray moon document across main build
Expand Down Expand Up @@ -61,6 +59,56 @@ def test_correct_p2sh_p2wsh_derivation():
assert spk_provider.fingerprint == "7c85f2b5".upper()


def test_unusual_derivations():
#######
key_origin = "m"
spk_provider = derive_spk_provider(
"spider manual inform reject arch raccoon betray moon document across main build",
key_origin,
network,
)
print(spk_provider)
assert spk_provider.key_origin == key_origin
assert (
spk_provider.xpub
== "tpubD6NzVbkrYhZ4YjD4x8pv3PDE9bzSdF6FLsCroncohJbjpx4X9KykvHvZt2E2ybcrAuiNXWkVMt8TuJxYV7YMcgkfytvjMoCssXpL6pUp4Sc"
)
# compared with sparrow
assert spk_provider.fingerprint == "7c85f2b5".upper()

#######
key_origin = "m/1234567"
spk_provider = derive_spk_provider(
"spider manual inform reject arch raccoon betray moon document across main build",
key_origin,
network,
)
print(spk_provider)
assert spk_provider.key_origin == key_origin
assert (
spk_provider.xpub
== "tpubD9BDKDcxLHML2kmMeCuMF5QBETNQncf35TnwPvC5qhtLZupbxLEa4mhrpZZzfgmL8cxVTVUhgmUticQNWipZd19zatMmwg7LLgYjmFkqXpM"
)
# compared with sparrow
assert spk_provider.fingerprint == "7c85f2b5".upper()

#######
key_origin = "m/1234567/0h/1"
spk_provider = derive_spk_provider(
"spider manual inform reject arch raccoon betray moon document across main build",
key_origin,
network,
)
print(spk_provider)
assert spk_provider.key_origin == key_origin
assert (
spk_provider.xpub
== "tpubDDXENRdKZmizWdFbbgVf37n9zZ6yykSUdLYfhoPG2ubUgBVaUShGvdU17BPRwNNtLRrt8jVayR96JoW8yg9TdDo9tg7LeCWCqJ9V7NFnQqL"
)
# compared with sparrow
assert spk_provider.fingerprint == "7c85f2b5".upper()


def test_correct_p2wsh_derivation():
spk_provider = derive_spk_provider(
"spider manual inform reject arch raccoon betray moon document across main build",
Expand Down Expand Up @@ -107,16 +155,6 @@ def test_correct_84derivation():
assert spk_provider.fingerprint == "7c85f2b5".upper()


def test_wrong_network():
with pytest.raises(ValueError) as exc_info:
xpub, fingerprint = derive(
"spider manual inform reject arch raccoon betray moon document across main build",
"m/48h/0h/0h/2h",
network,
)
assert str(exc_info.value) == "m/48h/0h/0h/2h does not fit the selected network Network.REGTEST"


def test_multisig():
spk_providers = [
derive_spk_provider(
Expand Down Expand Up @@ -175,3 +213,12 @@ def test_multisig_unusual_key_origin(caplog):
"m/44h/1h/0h does not match the default key origin m/48h/1h/0h/2h for this address type Multi Sig (SegWit/p2wsh)!"
== caplog.records[-1].message
)


def test_get_network_index():
assert get_network_index("m/48h/0h/0h/2h") == 0
assert get_network_index("m/48h/1h/0h/2h") == 1

assert get_network_index("m/48h/1/0h/2h") == None

assert get_network_index("m/4") == None
Loading