Skip to content

Commit

Permalink
Merge pull request #7 from andreasgriffin/add_derive
Browse files Browse the repository at this point in the history
Feature: Add derive to SoftwareSigner
  • Loading branch information
andreasgriffin authored Aug 19, 2024
2 parents 8c95209 + 9daeb70 commit 53dc201
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 30 deletions.
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

0 comments on commit 53dc201

Please sign in to comment.