Last active
January 24, 2024 15:44
-
-
Save kdmukai/7f4572488c29618c3e66ee4db0f74083 to your computer and use it in GitHub Desktop.
Extract and reuse outputs from a psbt using `embit`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from binascii import a2b_base64, unhexlify | |
from copy import deepcopy | |
from io import BytesIO | |
from embit.networks import NETWORKS | |
from embit.psbt import PSBT, OutputScope | |
# one input, one external recipient + change | |
psbt_base64 = "cHNidP8BAHECAAAAAU4T/0aX9mmNZHyKh+0AHYY+EtdJxndMRra0gn4QPCZdAQAAAAD9////Auj88QUAAAAAFgAUrME0Bwnpt7q+/5IYBvBXDGUr0FiQ0AMAAAAAABYAFGc8e3PH4CM45A3Z1h7cB0AEaAmTcAAAAE8BBDWHzwNXmUmVgAAAANRFa7R5gYD84Wbha3d1QnjgfYPOBw87on6cXS32WoyqAsPFtPxB7PRTdbujUnBPUVDh9YUBtwrl4nc0OcRNGvIyEA+4gv9UAACAAQAAgAAAAIAAAQCIAgAAAAHVNy3baqUJbmJM5kN9epW7oIqXB1O2s+Fs8julxND8ZQEAAAAXFgAUI+kCxhZQ0mdMSs6OSgKGdDKUanr9////As0uGh4BAAAAFgAUjiVTkQBkiXD8ylfqveCHXOprMQ4A4fUFAAAAABYAFFiMuj7Djc1P7mvOA8I27Lv2VmMObgAAAAEBHwDh9QUAAAAAFgAUWIy6PsONzU/ua84Dwjbsu/ZWYw4BAwQBAAAAIgYC9duqeSZYNc80SQfOc/SXZUUWqXZamBfjbIPdn18lj/cYD7iC/1QAAIABAACAAAAAgAAAAAADAAAAACICAiwV79CMgipih/G0K2ww7M7UfxxUhMPn1y52gKMFiT0nGA+4gv9UAACAAQAAgAAAAIABAAAAAAAAAAAA" | |
psbt = PSBT.parse(a2b_base64(psbt_base64)) | |
print(psbt.outputs) | |
# >> [OutputScope(2202022c15efd08c822a6287f1b42b6c30ecced47f1c5484c3e7d72e7680a305893d27180fb882ff540000800100008000000080010000000000000000), OutputScope(00)] | |
# First output is our change, second is an external recipient | |
# Must specify `version=2` so that the script_pubkey is included | |
change_output_data = psbt.outputs[0].serialize(version=2).hex() | |
external_recipient_output_data = psbt.outputs[1].serialize(version=2).hex() | |
# Sanity checks; restoring from the hex data should yield the same output | |
orig_change_output = psbt.outputs[0] | |
change_output = OutputScope.read_from(BytesIO(unhexlify(change_output_data))) | |
orig_recipient_output = psbt.outputs[1] | |
recipient_output = OutputScope.read_from(BytesIO(unhexlify(external_recipient_output_data))) | |
assert orig_change_output == change_output | |
assert orig_change_output.script_pubkey == change_output.script_pubkey | |
assert orig_change_output.script_pubkey.address(NETWORKS["regtest"]) == change_output.script_pubkey.address(NETWORKS["regtest"]) | |
assert orig_change_output.bip32_derivations == change_output.bip32_derivations | |
assert orig_change_output.value == orig_change_output.value | |
assert orig_recipient_output == recipient_output | |
assert orig_recipient_output.script_pubkey == recipient_output.script_pubkey | |
assert orig_recipient_output.script_pubkey.address(NETWORKS["regtest"]) == recipient_output.script_pubkey.address(NETWORKS["regtest"]) | |
assert orig_recipient_output.bip32_derivations == recipient_output.bip32_derivations | |
assert orig_recipient_output.value == recipient_output.value | |
# Verify that outputs can just be deleted or appended to a psbt | |
new_psbt = deepcopy(psbt) | |
new_psbt.outputs.clear() | |
assert new_psbt.serialize() != psbt.serialize() | |
new_psbt.outputs.append(change_output) | |
new_psbt.outputs.append(recipient_output) | |
assert new_psbt.serialize() == psbt.serialize() | |
# Okay, but output order matters! | |
new_psbt.outputs.clear() | |
new_psbt.outputs.append(recipient_output) | |
new_psbt.outputs.append(change_output) | |
assert new_psbt.serialize() != psbt.serialize() | |
# Now start from a different PSBT (two inputs, one taproot recipient + change) | |
psbt2_base64 = "cHNidP8BAKYCAAAAAsJ89R3J4RM3F/2mTME/ag5R+BIhKutmLi6PRugo3Y95AAAAAAD9////ThP/Rpf2aY1kfIqH7QAdhj4S10nGd0xGtrSCfhA8Jl0BAAAAAP3///8CkNADAAAAAAAiUSBx39QNrpwz8trkdbIaRO9k4gWnqpMPcyUdlq/n8IOaW+jd5wsAAAAAFgAUrME0Bwnpt7q+/5IYBvBXDGUr0FhwAAAATwEENYfPA1eZSZWAAAAA1EVrtHmBgPzhZuFrd3VCeOB9g84HDzuifpxdLfZajKoCw8W0/EHs9FN1u6NScE9RUOH1hQG3CuXidzQ5xE0a8jIQD7iC/1QAAIABAACAAAAAgAABAHECAAAAAfFB2o8nAMMw9ZFG4EHYvsMle9RkAw0xCXoVr12ZRrRqAQAAAAD9////AgDh9QUAAAAAFgAUkafX/Pco8lEPjPg2Vyd2MC0Cv8w/ijgMAQAAABYAFLHv1XF6upFAIyRP61rF77j3NegLbwAAAAEBHwDh9QUAAAAAFgAUkafX/Pco8lEPjPg2Vyd2MC0Cv8wBAwQBAAAAIgYCKALMbvmaFrAPq1M4ikZhDSVahyZhdJAi3ScfXi9k/JsYD7iC/1QAAIABAACAAAAAgAAAAAAFAAAAAAEAiAIAAAAB1Tct22qlCW5iTOZDfXqVu6CKlwdTtrPhbPI7pcTQ/GUBAAAAFxYAFCPpAsYWUNJnTErOjkoChnQylGp6/f///wLNLhoeAQAAABYAFI4lU5EAZIlw/MpX6r3gh1zqazEOAOH1BQAAAAAWABRYjLo+w43NT+5rzgPCNuy79lZjDm4AAAABAR8A4fUFAAAAABYAFFiMuj7Djc1P7mvOA8I27Lv2VmMOAQMEAQAAACIGAvXbqnkmWDXPNEkHznP0l2VFFql2WpgX42yD3Z9fJY/3GA+4gv9UAACAAQAAgAAAAIAAAAAAAwAAAAAAIgICLBXv0IyCKmKH8bQrbDDsztR/HFSEw+fXLnaAowWJPScYD7iC/1QAAIABAACAAAAAgAEAAAAAAAAAAA==" | |
psbt2 = PSBT.parse(a2b_base64(psbt2_base64)) | |
assert psbt.serialize() != psbt2.serialize() | |
# We can add the external recipient from the first PSBT as an additional output | |
psbt2.outputs.append(recipient_output) | |
# Just have to fix up the output amounts to make sense; reduce the change | |
# coming back. | |
psbt2.outputs[1].value -= psbt2.outputs[2].value | |
assert sum([output.value for output in psbt2.outputs]) == sum([inp.utxo.value for inp in psbt2.inputs]) - psbt2.fee() | |
# Can paste result into bip174.org and review | |
print(psbt2) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment