def from_tx(cls: Type[_Psbt], tx: Optional[Tx] = None, check_validity: bool = True) -> _Psbt: inputs = [] outputs = [] if tx: if tx.vin: for tx_in in tx.vin: tx_in.script_sig = b"" tx_in.script_witness = Witness() inputs = [PsbtIn() for _ in tx.vin] if tx.vout: outputs = [PsbtOut() for _ in tx.vout] else: # Creator places an unsigned transaction in the Psbt tx = Tx() # unsigned, unlocked, version 1 psbt_version = 0 hd_key_paths: Dict[Octets, BIP32KeyOrigin] = {} unknown: Dict[Octets, Octets] = {} return cls( tx, inputs, outputs, psbt_version, hd_key_paths, unknown, check_validity, )
def parse(cls: Type["Psbt"], psbt_bin: Octets, check_validity: bool = True) -> "Psbt": "Return a Psbt by parsing binary data." # FIXME: psbt_bin should be BinaryData # stream = bytesio_from_binarydata(psbt_bin) # and the deserialization should happen reading the stream # not slicing bytes tx = Tx(check_validity=False) version = 0 hd_key_paths: Dict[Octets, BIP32KeyOrigin] = {} unknown: Dict[Octets, Octets] = {} # psbt_bin = bytes_from_octets(psbt_bin) stream = bytesio_from_binarydata(psbt_bin) if stream.read(4) != PSBT_MAGIC_BYTES: raise BTClibValueError("malformed psbt: missing magic bytes") if stream.read(1) != PSBT_SEPARATOR: raise BTClibValueError("malformed psbt: missing separator") global_map, stream = deserialize_map(stream) for k, v in global_map.items(): if k[:1] == PSBT_GLOBAL_UNSIGNED_TX: tx = deserialize_tx(k, v, "global unsigned tx", False) elif k[:1] == PSBT_GLOBAL_VERSION: version = deserialize_int(k, v, "global version") elif k[:1] == PSBT_GLOBAL_XPUB: hd_key_paths[k[1:]] = BIP32KeyOrigin.parse(v) else: # unknown unknown[k] = v inputs: List[PsbtIn] = [] for _ in tx.vin: input_map, stream = deserialize_map(stream) inputs.append(PsbtIn.parse(input_map)) outputs: List[PsbtOut] = [] for _ in tx.vout: output_map, stream = deserialize_map(stream) outputs.append(PsbtOut.parse(output_map)) return cls( tx, inputs, outputs, version, hd_key_paths, unknown, check_validity, )
def from_dict(cls: Type["Psbt"], dict_: Mapping[str, Any], check_validity: bool = True) -> "Psbt": return cls( Tx.from_dict(dict_["tx"]), [PsbtIn.from_dict(psbt_in, False) for psbt_in in dict_["inputs"]], [ PsbtOut.from_dict(psbt_out, False) for psbt_out in dict_["outputs"] ], dict_["version"], # FIXME decode_from_bip32_derivs(dict_["bip32_derivs"]), # type: ignore dict_["unknown"], check_validity, )
def test_dataclasses_json_dict() -> None: psbt_str = "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgf0cwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMASICAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAQEDBAEAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHIgIDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtxHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwEiAgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc0cwRAIgZfRbpZmLWaJ//hp77QFq8fH5DVSzqo90UKpfVqJRA70CIH9yRwOtHtuWaAsoS1bU/8uI9/t1nqu+CKow8puFE4PSAQEDBAEAAAABBCIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA" psbt = Psbt.b64decode(psbt_str) # PsbtOut dataclass psbt_out = psbt.outputs[0] assert isinstance(psbt_out, PsbtOut) # PsbtOut dataclass to dict psbt_out_dict = psbt_out.to_dict() assert isinstance(psbt_out_dict, dict) assert psbt_out_dict["redeem_script"] == "" assert psbt_out_dict["witness_script"] == "" assert psbt_out_dict["bip32_derivs"] assert psbt_out_dict["unknown"] == {} # PsbtOut dataclass dict to file datadir = path.join(path.dirname(__file__), "_generated_files") filename = path.join(datadir, "psbt_out.json") with open(filename, "w") as file_: json.dump(psbt_out_dict, file_, indent=4) # PsbtOut dataclass dict from file with open(filename, "r") as file_: psbt_out_dict2 = json.load(file_) assert isinstance(psbt_out_dict2, dict) assert psbt_out_dict2["redeem_script"] == "" assert psbt_out_dict2["witness_script"] == "" assert psbt_out_dict2["bip32_derivs"] assert psbt_out_dict2["unknown"] == {} assert psbt_out_dict == psbt_out_dict2 # PsbtOut dataclass from dict psbt_out2 = PsbtOut.from_dict(psbt_out_dict) assert isinstance(psbt_out2, PsbtOut) assert psbt_out == psbt_out2
def from_tx(cls: Type["Psbt"], tx: Tx, check_validity: bool = True) -> "Psbt": for tx_in in tx.vin: tx_in.script_sig = b"" tx_in.script_witness = Witness() inputs = [PsbtIn() for _ in tx.vin] outputs = [PsbtOut() for _ in tx.vout] psbt_version = 0 hd_key_paths: Dict[Octets, BIP32KeyOrigin] = {} unknown: Dict[Octets, Octets] = {} return cls( tx, inputs, outputs, psbt_version, hd_key_paths, unknown, check_validity, )
def test_psbt_out() -> None: psbt_out = PsbtOut() # FIXME # assert psbt_out == PsbtOut.parse(psbt_out.serialize()) assert psbt_out == PsbtOut.from_dict(psbt_out.to_dict())