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

Add PEP484 type hints #17

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
44 changes: 25 additions & 19 deletions bip380/descriptors/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from __future__ import annotations

from typing import List, TYPE_CHECKING

from bip380.key import DescriptorKey
from bip380.miniscript import Node
from bip380.utils.hashes import sha256, hash160
Expand All @@ -8,40 +12,42 @@
OP_EQUALVERIFY,
OP_CHECKSIG,
)

from .checksum import descsum_create
from .parsing import descriptor_from_str

if TYPE_CHECKING:
from bip380.miniscript import SatisfactionMaterial


class Descriptor:
"""A Bitcoin Output Script Descriptor."""

def from_str(desc_str, strict=False):
def from_str(desc_str: str, strict: bool = False) -> Descriptor:
"""Parse a Bitcoin Output Script Descriptor from its string representation.

:param strict: whether to require the presence of a checksum.
"""
return descriptor_from_str(desc_str, strict)

@property
def script_pubkey(self):
def script_pubkey(self) -> CScript:
"""Get the ScriptPubKey (output 'locking' Script) for this descriptor."""
# To be implemented by derived classes
raise NotImplementedError

@property
def script_sighash(self):
def script_sighash(self) -> CScript:
"""Get the Script to be committed to by the signature hash of a spending transaction."""
# To be implemented by derived classes
raise NotImplementedError

@property
def keys(self):
def keys(self) -> List[DescriptorKey]:
"""Get the list of all keys from this descriptor, in order of apparition."""
# To be implemented by derived classes
raise NotImplementedError

def derive(self, index):
def derive(self, index: int) -> None:
"""Derive the key at the given derivation index.

A no-op if the key isn't a wildcard. Will start from 2**31 if the key is a "hardened
Expand All @@ -65,27 +71,27 @@ def satisfy(self, *args, **kwargs):
class WshDescriptor(Descriptor):
"""A Segwit v0 P2WSH Output Script Descriptor."""

def __init__(self, witness_script):
def __init__(self, witness_script: Node):
assert isinstance(witness_script, Node)
self.witness_script = witness_script
self.witness_script: Node = witness_script

def __repr__(self):
def __repr__(self) -> str:
return descsum_create(f"wsh({self.witness_script})")

@property
def script_pubkey(self):
def script_pubkey(self) -> CScript:
witness_program = sha256(self.witness_script.script)
return CScript([0, witness_program])

@property
def script_sighash(self):
def script_sighash(self) -> CScript:
return self.witness_script.script

@property
def keys(self):
def keys(self) -> List[DescriptorKey]:
return self.witness_script.keys

def satisfy(self, sat_material=None):
def satisfy(self, sat_material: SatisfactionMaterial = None) -> List[Node]:
"""Get the witness stack to spend from this descriptor.

:param sat_material: a miniscript.satisfaction.SatisfactionMaterial with data
Expand All @@ -99,28 +105,28 @@ def satisfy(self, sat_material=None):
class WpkhDescriptor(Descriptor):
"""A Segwit v0 P2WPKH Output Script Descriptor."""

def __init__(self, pubkey):
def __init__(self, pubkey: DescriptorKey):
assert isinstance(pubkey, DescriptorKey)
self.pubkey = pubkey

def __repr__(self):
def __repr__(self) -> str:
return descsum_create(f"wpkh({self.pubkey})")

@property
def script_pubkey(self):
def script_pubkey(self) -> CScript:
witness_program = hash160(self.pubkey.bytes())
return CScript([0, witness_program])

@property
def script_sighash(self):
def script_sighash(self) -> CScript:
key_hash = hash160(self.pubkey.bytes())
return CScript([OP_DUP, OP_HASH160, key_hash, OP_EQUALVERIFY, OP_CHECKSIG])

@property
def keys(self):
def keys(self) -> List[DescriptorKey]:
return [self.pubkey]

def satisfy(self, signature):
def satisfy(self, signature: bytes) -> List[bytes]:
"""Get the witness stack to spend from this descriptor.

:param signature: a signature (in bytes) for the pubkey from the descriptor.
Expand Down
11 changes: 6 additions & 5 deletions bip380/descriptors/checksum.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
"""Utility functions related to output descriptors"""

import re
from typing import List, Optional

INPUT_CHARSET = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ "
CHECKSUM_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
GENERATOR = [0xF5DEE51989, 0xA9FDCA3312, 0x1BAB10E32D, 0x3706B1677A, 0x644D626FFD]


def descsum_polymod(symbols):
def descsum_polymod(symbols: List[int]) -> int:
"""Internal function that computes the descriptor checksum."""
chk = 1
for value in symbols:
Expand All @@ -22,7 +23,7 @@ def descsum_polymod(symbols):
return chk


def descsum_expand(s):
def descsum_expand(s: str) -> Optional[List[int]]:
"""Internal function that does the character to symbol expansion"""
groups = []
symbols = []
Expand All @@ -42,7 +43,7 @@ def descsum_expand(s):
return symbols


def descsum_create(s):
def descsum_create(s: str) -> str:
"""Add a checksum to a descriptor without"""
symbols = descsum_expand(s) + [0, 0, 0, 0, 0, 0, 0, 0]
checksum = descsum_polymod(symbols) ^ 1
Expand All @@ -53,7 +54,7 @@ def descsum_create(s):
)


def descsum_check(s):
def descsum_check(s: str) -> bool:
"""Verify that the checksum is correct in a descriptor"""
if s[-9] != "#":
return False
Expand All @@ -63,7 +64,7 @@ def descsum_check(s):
return descsum_polymod(symbols) == 1


def drop_origins(s):
def drop_origins(s: str) -> str:
"""Drop the key origins from a descriptor"""
desc = re.sub(r"\[.+?\]", "", s)
if "#" in s:
Expand Down
4 changes: 2 additions & 2 deletions bip380/descriptors/errors.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class DescriptorParsingError(ValueError):
"""Error while parsing a Bitcoin Output Descriptor from its string representation"""

def __init__(self, message):
self.message = message
def __init__(self, message: str):
self.message: str = message
6 changes: 4 additions & 2 deletions bip380/descriptors/parsing.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import bip380.descriptors as descriptors

from bip380.key import DescriptorKey, DescriptorKeyError
Expand All @@ -7,7 +9,7 @@
from .errors import DescriptorParsingError


def split_checksum(desc_str, strict=False):
def split_checksum(desc_str: str, strict: bool = False) -> str:
"""Removes and check the provided checksum.
If not told otherwise, this won't fail on a missing checksum.

Expand All @@ -28,7 +30,7 @@ def split_checksum(desc_str, strict=False):
return descriptor


def descriptor_from_str(desc_str, strict=False):
def descriptor_from_str(desc_str: str, strict: bool = False) -> descriptors.Descriptor:
"""Parse a Bitcoin Output Script Descriptor from its string representation.

:param strict: whether to require the presence of a checksum.
Expand Down
43 changes: 26 additions & 17 deletions bip380/key.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
from __future__ import annotations

from enum import Enum, auto
from typing import List, Optional, Union

from bip32 import BIP32
from bip32.utils import coincurve, _deriv_path_str_to_list

from bip380.utils.hashes import hash160
from enum import Enum, auto


class DescriptorKeyError(Exception):
def __init__(self, message):
self.message = message
def __init__(self, message: str):
self.message: str = message


class DescriporKeyOrigin:
Expand All @@ -15,13 +20,13 @@ class DescriporKeyOrigin:
See https://github.com/bitcoin/bips/blob/master/bip-0380.mediawiki#key-expressions.
"""

def __init__(self, fingerprint, path):
def __init__(self, fingerprint: bytes, path: List[int]):
assert isinstance(fingerprint, bytes) and isinstance(path, list)

self.fingerprint = fingerprint
self.path = path
self.fingerprint: bytes = fingerprint
self.path: List[int] = path

def from_str(origin_str):
def from_str(origin_str: str) -> DescriporKeyOrigin:
# Origing starts and ends with brackets
if not origin_str.startswith("[") or not origin_str.endswith("]"):
raise DescriptorKeyError(f"Insane origin: '{origin_str}'")
Expand Down Expand Up @@ -54,7 +59,7 @@ class KeyPathKind(Enum):
WILDCARD_UNHARDENED = auto()
WILDCARD_HARDENED = auto()

def is_wildcard(self):
def is_wildcard(self) -> bool:
return self in [KeyPathKind.WILDCARD_HARDENED, KeyPathKind.WILDCARD_UNHARDENED]


Expand All @@ -64,13 +69,13 @@ class DescriptorKeyPath:
See https://github.com/bitcoin/bips/blob/master/bip-0380.mediawiki#key-expressions.
"""

def __init__(self, path, kind):
def __init__(self, path: List[int], kind: KeyPathKind):
assert isinstance(path, list) and isinstance(kind, KeyPathKind)

self.path = path
self.kind = kind
self.path: List[int] = path
self.kind: KeyPathKind = kind

def from_str(path_str):
def from_str(path_str: str) -> DescriptorKeyPath:
if len(path_str) < 1:
raise DescriptorKeyError(f"Insane key path: '{path_str}'")
if path_str[0] == "/":
Expand Down Expand Up @@ -108,7 +113,11 @@ class DescriptorKey:
May be an extended or raw public key.
"""

def __init__(self, key):
origin: Optional[DescriporKeyOrigin]
path: Optional[DescriptorKeyPath]
key: Union[coincurve.PublicKey, BIP32]

def __init__(self, key: Union[bytes, BIP32, str]):
# Information about the origin of this key.
self.origin = None
# If it is an xpub, a path toward a child key of that xpub.
Expand Down Expand Up @@ -156,10 +165,10 @@ def __init__(self, key):
"Invalid parameter type: expecting bytes, hex str or BIP32 instance."
)

def __repr__(self):
def __repr__(self) -> str:
key = ""

def ser_path(key, path):
def ser_path(key: str, path: List[int]) -> str:
for i in path:
if i < 2**31:
key += f"/{i}"
Expand All @@ -185,7 +194,7 @@ def ser_path(key, path):

return key

def bytes(self):
def bytes(self) -> bytes:
if isinstance(self.key, coincurve.PublicKey):
return self.key.format()
else:
Expand All @@ -195,7 +204,7 @@ def bytes(self):
assert not self.path.kind.is_wildcard() # TODO: real errors
return self.key.get_pubkey_from_path(self.path.path)

def derive(self, index):
def derive(self, index: int) -> None:
"""Derive the key at the given index.

A no-op if the key isn't a wildcard. Will start from 2**31 if the key is a "hardened
Expand Down
8 changes: 4 additions & 4 deletions bip380/miniscript/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@


class MiniscriptNodeCreationError(ValueError):
def __init__(self, message):
self.message = message
def __init__(self, message: str):
self.message: str = message


class MiniscriptPropertyError(ValueError):
def __init__(self, message):
self.message = message
def __init__(self, message: str):
self.message: str = message

# TODO: errors for type errors, parsing errors, etc..
Loading