diff --git a/btclib/alias.py b/btclib/alias.py index bc198b3e4..d262590dd 100644 --- a/btclib/alias.py +++ b/btclib/alias.py @@ -133,8 +133,5 @@ # or Octets of its byte-encoded representation Script = Union[Octets, List[ScriptToken]] -# A fingerprint is represented as 4 bytes or his string representation -Fingerprint = Union[str, bytes] - # Object that can be textually saved without any conversion Printable = Union[int, str] diff --git a/btclib/psbt.py b/btclib/psbt.py index 58a49db12..18d4aa46d 100644 --- a/btclib/psbt.py +++ b/btclib/psbt.py @@ -21,12 +21,11 @@ from dataclasses_json import DataClassJsonMixin, config from . import der, script, varint -from .alias import Fingerprint, ScriptToken, String +from .alias import Octets, ScriptToken, String from .bip32 import ( BIP32KeyData, BIP32Path, bytes_from_bip32_path, - indexes_from_bip32_path, str_from_bip32_path, ) from .der import DERSig @@ -37,6 +36,7 @@ from .tx_in import witness_deserialize, witness_serialize from .tx_out import TxOut from .utils import ( + bytes_from_octets, hash160, sha256, token_or_string_to_hex_string, @@ -55,35 +55,24 @@ def _pubkey_to_hex_string(pubkey: PubKey) -> str: return pubkey.hex() -def _fingerprint_to_hex_string(fingerprint: Fingerprint) -> str: - if isinstance(fingerprint, str): - return fingerprint - else: - return fingerprint.hex() - - @dataclass class HdKeypaths(DataClassJsonMixin): hd_keypaths: Dict[str, Dict[str, str]] = field(default_factory=dict) - def add_hd_path( - self, key: PubKey, fingerprint: Fingerprint, path: BIP32Path - ) -> None: - - fingerprint_str = _fingerprint_to_hex_string(fingerprint) - indexes = indexes_from_bip32_path(path) - idx = [index.to_bytes(4, "big") for index in indexes] + def add_hd_path(self, key: PubKey, fingerprint: Octets, path: BIP32Path) -> None: - path_str = str_from_bip32_path(b"".join(idx), "big") key_str = _pubkey_to_hex_string(key) - + # assert key_str == pubkeyinfo_from_key(key)[0].hex() + fingerprint_str = bytes_from_octets(fingerprint, 4).hex() + path_str = str_from_bip32_path(path, "little") self.hd_keypaths[key_str] = { "fingerprint": fingerprint_str, "derivation_path": path_str, } def get_hd_path_entry(self, key: PubKey) -> Tuple[str, str]: - entry = self.hd_keypaths[_pubkey_to_hex_string(key)] + key_str = _pubkey_to_hex_string(key) + entry = self.hd_keypaths[key_str] return entry["fingerprint"], entry["derivation_path"] @@ -99,7 +88,8 @@ def add_sig(self, key: PubKey, sig: DERSig): self.sigs[key_str] = sig_str def get_sig(self, key: PubKey) -> str: - return self.sigs[_pubkey_to_hex_string(key)] + key_str = _pubkey_to_hex_string(key) + return self.sigs[key_str] _PsbtIn = TypeVar("_PsbtIn", bound="PsbtIn") @@ -165,17 +155,9 @@ def deserialize( assert len(key) == 1 witness_script = script.decode(value) elif key[0] == 0x06: - assert len(key) == 33 + 1 - assert len(value) % 4 == 0 - path = value[4:] - hd_keypaths.add_hd_path( - key[1:], - value[:4], - [ - int.from_bytes(path[i : i + 4], "little") - for i in range(0, len(path), 4) - ], - ) + if len(key) != 33 + 1: + raise ValueError(f"invalid key lenght: {len(key)-1}") + hd_keypaths.add_hd_path(key[1:], value[:4], value[4:]) elif key[0] == 0x07: assert len(key) == 1 final_script_sig = script.decode(value) @@ -314,17 +296,9 @@ def deserialize( assert len(key) == 1 witness_script = script.decode(value) elif key[0] == 0x02: - assert len(key) == 33 + 1 - assert len(value) % 4 == 0 - path = value[4:] - hd_keypaths.add_hd_path( - key[1:], - value[:4], - [ - int.from_bytes(path[i : i + 4], "little") - for i in range(0, len(path), 4) - ], - ) + if len(key) != 33 + 1: + raise ValueError(f"invalid key lenght: {len(key)-1}") + hd_keypaths.add_hd_path(key[1:], value[:4], value[4:]) elif key[0] == 0xFC: # proprietary use prefix = varint.decode(key[1:]) if prefix not in proprietary.keys(): @@ -420,18 +394,11 @@ def deserialize(cls: Type[_PSbt], string: str, assert_valid: bool = True) -> _PS if key[0] == 0x00: assert len(key) == 1 tx = Tx.deserialize(value) - elif key[0] == 0x01: # TODO - assert len(key) == 78 + 1 - assert len(value) % 4 == 0 - path = value[4:] - hd_keypaths.add_hd_path( - key[1:], - value[:4], - [ - int.from_bytes(path[i : i + 4], "little") - for i in range(0, len(path), 4) - ], - ) + elif key[0] == 0x01: + # TODO add test case + # why extended key here? + assert len(key) == 78 + 1, f"invalid key lenght: {len(key)-1}" + hd_keypaths.add_hd_path(key[1:], value[:4], value[4:]) elif key[0] == 0xFB: assert len(value) == 4 version = int.from_bytes(value, "little") diff --git a/btclib/tests/test_psbt.py b/btclib/tests/test_psbt.py index b2640cca3..1788a408e 100644 --- a/btclib/tests/test_psbt.py +++ b/btclib/tests/test_psbt.py @@ -85,7 +85,7 @@ def test_invalid_input_typed_keys(): Psbt.deserialize(psbt_string) psbt_string = "cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEBIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIgIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUZGMEMCIAQktY7/qqaU4VWepck7v9SokGQiQFXN8HC2dxRpRC0HAh9cjrD+plFtYLisszrWTt5g6Hhb+zqpS5m9+GFR25qaAQEEIgAgdx/RitRZZm3Unz1WTj28QvTIR3TjYK2haBao7UiNVoEBBUdSIQOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RiED3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg71SriEGA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb0QtKa6ZwAAAIAAAACABAAAgCIGA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9ELSmumcAAACAAAAAgAUAAIAAAA==" - with pytest.raises(AssertionError): + with pytest.raises(ValueError, match="invalid key lenght: "): Psbt.deserialize(psbt_string) psbt_string = "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAIAALsCAAAAAarXOTEBi9JfhK5AC2iEi+CdtwbqwqwYKYur7nGrZW+LAAAAAEhHMEQCIFj2/HxqM+GzFUjUgcgmwBW9MBNarULNZ3kNq2bSrSQ7AiBKHO0mBMZzW2OT5bQWkd14sA8MWUL7n3UYVvqpOBV9ugH+////AoDw+gIAAAAAF6kUD7lGNCFpa4LIM68kHHjBfdveSTSH0PIKJwEAAAAXqRQpynT4oI+BmZQoGFyXtdhS5AY/YYdlAAAAAQfaAEcwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMAUgwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gFHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4AAQEgAMLrCwAAAAAXqRS39fr0Dj1ApaRZsds1NfK3L6kh6IcBByMiACCMI1MXN0O1ld+0oHtyuo5C43l9p06H/n2ddJfjsgKJAwEI2gQARzBEAiBi63pVYQenxz9FrEq1od3fb3B1+xJ1lpp/OD7/94S8sgIgDAXbt0cNvy8IVX3TVscyXB7TCRPpls04QJRdsSIo2l8BRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA=" @@ -108,7 +108,7 @@ def test_invalid_input_typed_keys(): def test_invalid_output_typed_keys(): psbt_string = "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABB9oARzBEAiB0AYrUGACXuHMyPAAVcgs2hMyBI4kQSOfbzZtVrWecmQIgc9Npt0Dj61Pc76M4I8gHBRTKVafdlUTxV8FnkTJhEYwBSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAUdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSrgABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEHIyIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQjaBABHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwFHMEQCIGX0W6WZi1mif/4ae+0BavHx+Q1Us6qPdFCqX1aiUQO9AiB/ckcDrR7blmgLKEtW1P/LiPf7dZ6rvgiqMPKbhROD0gFHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4AIQIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1PtnuylhxDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA" - with pytest.raises(AssertionError): + with pytest.raises(ValueError, match="invalid key lenght: "): Psbt.deserialize(psbt_string) psbt_string = "cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAgAAFgAUYunpgv/zTdgjlhAxawkM0qO3R8sAAQAiACCHa62DLx0WgBXtQSMqnqZaGBXZ7xPA74dZ9ktbKyeKZQEBJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A"