From 5105854621e622e1482d39ea9c5db1d92bff2829 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Fri, 5 Jan 2024 20:11:25 -0600 Subject: [PATCH 01/15] Initial support for OP_RETURN display --- src/seedsigner/gui/screens/psbt_screens.py | 25 ++++++++++++++ src/seedsigner/models/psbt_parser.py | 4 +++ src/seedsigner/views/psbt_views.py | 40 ++++++++++++++++++++-- 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/seedsigner/gui/screens/psbt_screens.py b/src/seedsigner/gui/screens/psbt_screens.py index 330742d5..5515278e 100644 --- a/src/seedsigner/gui/screens/psbt_screens.py +++ b/src/seedsigner/gui/screens/psbt_screens.py @@ -21,6 +21,7 @@ class PSBTOverviewScreen(ButtonListScreen): num_self_transfer_outputs: int = 0 num_change_outputs: int = 0 destination_addresses: List[str] = None + has_op_return: bool = False def __post_init__(self): @@ -143,6 +144,9 @@ def truncate_destination_addr(addr): destination_column.append(f"fee") + if self.has_op_return: + destination_column.append("OP_RETURN") + if self.num_change_outputs > 0: for i in range(0, self.num_change_outputs): destination_column.append("change") @@ -681,6 +685,27 @@ def __post_init__(self): +@dataclass +class PSBTOpReturnScreen(ButtonListScreen): + op_return: str = None + + def __post_init__(self): + # Customize defaults + self.is_bottom_list = True + + super().__post_init__() + + self.components.append(TextArea( + text=self.op_return, + font_size=GUIConstants.TOP_NAV_TITLE_FONT_SIZE + 2, + is_text_centered=True, + # edge_padding=GUIConstants.EDGE_PADDING * 2, + screen_y=self.top_nav.height + GUIConstants.COMPONENT_PADDING, + height=self.buttons[0].screen_y - self.top_nav.height - 2*GUIConstants.COMPONENT_PADDING, + )) + + + @dataclass class PSBTFinalizeScreen(ButtonListScreen): def __post_init__(self): diff --git a/src/seedsigner/models/psbt_parser.py b/src/seedsigner/models/psbt_parser.py index a20c13cc..7acaabcf 100644 --- a/src/seedsigner/models/psbt_parser.py +++ b/src/seedsigner/models/psbt_parser.py @@ -195,6 +195,10 @@ def _parse_outputs(self): }) self.change_amount += self.psbt.tx.vout[i].value + elif self.psbt.tx.vout[i].value == 0 and self.psbt.tx.vout[i].script_pubkey.script_type() is None and self.psbt.tx.vout[i].script_pubkey.data: + # OP_RETURN + self.op_return = self.psbt.tx.vout[i].script_pubkey.data[3:].decode('UTF-8') + else: addr = self.psbt.tx.vout[i].script_pubkey.address(NETWORKS[SettingsConstants.map_network_to_embit(self.network)]) self.destination_addresses.append(addr) diff --git a/src/seedsigner/views/psbt_views.py b/src/seedsigner/views/psbt_views.py index 18e40312..e8ac194e 100644 --- a/src/seedsigner/views/psbt_views.py +++ b/src/seedsigner/views/psbt_views.py @@ -7,7 +7,7 @@ from seedsigner.models.encode_qr import UrPsbtQrEncoder from seedsigner.models.psbt_parser import PSBTParser from seedsigner.models.settings import SettingsConstants -from seedsigner.gui.screens.psbt_screens import PSBTOverviewScreen, PSBTMathScreen, PSBTAddressDetailsScreen, PSBTChangeDetailsScreen, PSBTFinalizeScreen +from seedsigner.gui.screens.psbt_screens import PSBTOpReturnScreen, PSBTOverviewScreen, PSBTMathScreen, PSBTAddressDetailsScreen, PSBTChangeDetailsScreen, PSBTFinalizeScreen from seedsigner.gui.screens.screen import (RET_CODE__BACK_BUTTON, ButtonListScreen, WarningScreen, DireWarningScreen, QRDisplayScreen) from seedsigner.views.view import BackStackView, MainMenuView, NotYetImplementedView, View, Destination @@ -137,7 +137,8 @@ def run(self): num_inputs=psbt_parser.num_inputs, num_self_transfer_outputs=num_self_transfer_outputs, num_change_outputs=num_change_outputs, - destination_addresses=psbt_parser.destination_addresses + destination_addresses=psbt_parser.destination_addresses, + has_op_return=psbt_parser.op_return is not None, ) if selected_menu_num == RET_CODE__BACK_BUTTON: @@ -276,12 +277,14 @@ def run(self): # Move on to display change return Destination(PSBTChangeDetailsView, view_args={"change_address_num": 0}) + elif psbt_parser.op_return: + return Destination(PSBTOpReturnView) + else: # There's no change output to verify. Move on to sign the PSBT. return Destination(PSBTFinalizeView) - class PSBTChangeDetailsView(View): NEXT = "Next" SKIP_VERIFICATION = "Skip Verificiation" @@ -417,6 +420,10 @@ def run(self): if self.change_address_num < psbt_parser.num_change_outputs - 1: return Destination(PSBTChangeDetailsView, view_args={"change_address_num": self.change_address_num + 1}) + + elif psbt_parser.op_return: + return Destination(PSBTOpReturnView) + else: # There's no more change to verify. Move on to sign the PSBT. return Destination(PSBTFinalizeView) @@ -456,6 +463,33 @@ def run(self): return Destination(MainMenuView, clear_history=True) +class PSBTOpReturnView(View): + """ + Shows the OP_RETURN data + """ + def run(self): + psbt_parser: PSBTParser = self.controller.psbt_parser + + if not psbt_parser: + # Should not be able to get here + raise Exception("Routing error") + + title = "OP_RETURN" + button_data = ["Next"] + + selected_menu_num = self.run_screen( + PSBTOpReturnScreen, + title=title, + button_data=button_data, + op_return=psbt_parser.op_return, + ) + + if selected_menu_num == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) + + return Destination(PSBTFinalizeView) + + class PSBTFinalizeView(View): """ From ce2974718507fd7fa1bce6b9d26d75aba3581a71 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Sat, 6 Jan 2024 08:35:07 -0600 Subject: [PATCH 02/15] Proper detection of OP_RETURN outputs --- src/seedsigner/models/psbt_parser.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/seedsigner/models/psbt_parser.py b/src/seedsigner/models/psbt_parser.py index 7acaabcf..a7b4e0d7 100644 --- a/src/seedsigner/models/psbt_parser.py +++ b/src/seedsigner/models/psbt_parser.py @@ -10,6 +10,10 @@ from seedsigner.models.settings import SettingsConstants +class OPCODES: + OP_RETURN = 106 + OP_PUSHDATA1 = 76 + class PSBTParser(): def __init__(self, p: PSBT, seed: Seed, network: str = SettingsConstants.MAINNET): @@ -169,7 +173,11 @@ def _parse_outputs(self): if sc.data == self.psbt.tx.vout[i].script_pubkey.data: is_change = True - if is_change: + if self.psbt.tx.vout[i].script_pubkey.data[0] == OPCODES.OP_RETURN: + # data = OP_RETURN + OP_PUSHDATA1 + len(payload) + payload + self.op_return = self.psbt.tx.vout[i].script_pubkey.data[3:].decode('UTF-8') + + elif is_change: addr = self.psbt.tx.vout[i].script_pubkey.address(NETWORKS[SettingsConstants.map_network_to_embit(self.network)]) fingerprints = [] derivation_paths = [] @@ -195,10 +203,6 @@ def _parse_outputs(self): }) self.change_amount += self.psbt.tx.vout[i].value - elif self.psbt.tx.vout[i].value == 0 and self.psbt.tx.vout[i].script_pubkey.script_type() is None and self.psbt.tx.vout[i].script_pubkey.data: - # OP_RETURN - self.op_return = self.psbt.tx.vout[i].script_pubkey.data[3:].decode('UTF-8') - else: addr = self.psbt.tx.vout[i].script_pubkey.address(NETWORKS[SettingsConstants.map_network_to_embit(self.network)]) self.destination_addresses.append(addr) From 6e2faf15dfcf66ae2c09556c9d857bb76faf83fd Mon Sep 17 00:00:00 2001 From: kdmukai Date: Mon, 8 Jan 2024 15:05:10 -0600 Subject: [PATCH 03/15] Move display code to Screen; handle undecodable OP_RETURN bytes --- src/seedsigner/gui/screens/psbt_screens.py | 47 ++++++++++++++++++---- src/seedsigner/models/psbt_parser.py | 6 ++- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/seedsigner/gui/screens/psbt_screens.py b/src/seedsigner/gui/screens/psbt_screens.py index 5515278e..de74d6ba 100644 --- a/src/seedsigner/gui/screens/psbt_screens.py +++ b/src/seedsigner/gui/screens/psbt_screens.py @@ -1,4 +1,5 @@ from dataclasses import dataclass +import math from PIL import Image, ImageDraw, ImageFilter from typing import List import time @@ -695,14 +696,44 @@ def __post_init__(self): super().__post_init__() - self.components.append(TextArea( - text=self.op_return, - font_size=GUIConstants.TOP_NAV_TITLE_FONT_SIZE + 2, - is_text_centered=True, - # edge_padding=GUIConstants.EDGE_PADDING * 2, - screen_y=self.top_nav.height + GUIConstants.COMPONENT_PADDING, - height=self.buttons[0].screen_y - self.top_nav.height - 2*GUIConstants.COMPONENT_PADDING, - )) + font_size = GUIConstants.TOP_NAV_TITLE_FONT_SIZE + 2 + + try: + # Simple case: display human-readable text + self.components.append(TextArea( + text=self.op_return.decode(errors="strict"), + font_size=font_size, + is_text_centered=True, + allow_text_overflow=True, + screen_y=self.top_nav.height + GUIConstants.COMPONENT_PADDING, + height=self.buttons[0].screen_y - self.top_nav.height - 2*GUIConstants.COMPONENT_PADDING, + )) + return + except UnicodeDecodeError: + # Contains data that can't be converted to UTF-8; probably encoded and not + # meant to be human readable. + chars_per_line = 16 + decoded_str = self.op_return.decode(errors="ignore") + num_lines = math.ceil(len(decoded_str) / chars_per_line) + text = "" + for i in range(num_lines): + text += (decoded_str[i*chars_per_line:(i+1)*chars_per_line]) + "\n" + text = text[:-1] + + label = TextArea( + text="Encoded bytes", + font_color=GUIConstants.LABEL_FONT_COLOR, + font_size=GUIConstants.LABEL_FONT_SIZE, + screen_y=self.top_nav.height + GUIConstants.COMPONENT_PADDING * 2, + ) + self.components.append(label) + + self.components.append(TextArea( + text=text, + # font_name=GUIConstants.FIXED_WIDTH_FONT_NAME, + font_size=font_size, + screen_y=label.screen_y + label.height + GUIConstants.COMPONENT_PADDING, + )) diff --git a/src/seedsigner/models/psbt_parser.py b/src/seedsigner/models/psbt_parser.py index a7b4e0d7..b0af2ff8 100644 --- a/src/seedsigner/models/psbt_parser.py +++ b/src/seedsigner/models/psbt_parser.py @@ -15,6 +15,7 @@ class OPCODES: OP_PUSHDATA1 = 76 + class PSBTParser(): def __init__(self, p: PSBT, seed: Seed, network: str = SettingsConstants.MAINNET): self.psbt: PSBT = p @@ -30,6 +31,7 @@ def __init__(self, p: PSBT, seed: Seed, network: str = SettingsConstants.MAINNET self.num_inputs = 0 self.destination_addresses = [] self.destination_amounts = [] + self.op_return = None self.root = None @@ -174,8 +176,8 @@ def _parse_outputs(self): is_change = True if self.psbt.tx.vout[i].script_pubkey.data[0] == OPCODES.OP_RETURN: - # data = OP_RETURN + OP_PUSHDATA1 + len(payload) + payload - self.op_return = self.psbt.tx.vout[i].script_pubkey.data[3:].decode('UTF-8') + # The data is written as: OP_RETURN + OP_PUSHDATA1 + len(payload) + payload + self.op_return = self.psbt.tx.vout[i].script_pubkey.data[3:] elif is_change: addr = self.psbt.tx.vout[i].script_pubkey.address(NETWORKS[SettingsConstants.map_network_to_embit(self.network)]) From 3165a4345b05ae2988f9f2a16e5a1ce97f12a9dd Mon Sep 17 00:00:00 2001 From: kdmukai Date: Mon, 8 Jan 2024 15:05:43 -0600 Subject: [PATCH 04/15] Add PSBTParser test case --- tests/test_psbt_parser.py | 42 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/test_psbt_parser.py b/tests/test_psbt_parser.py index cf6f8db2..90175ce6 100644 --- a/tests/test_psbt_parser.py +++ b/tests/test_psbt_parser.py @@ -112,3 +112,45 @@ def test_p2sh_legacy_multisig(): # And the self-transfer receive addr assert psbt_parser.verify_multisig_output(descriptor, 1) + + +def test_parse_op_return_content(): + """ + Should successfully parse the OP_RETURN content from a PSBT. + + PSBT Tx and Wallet Details + - Single Sig Wallet P2WPKH (Native Segwit) with no passphrase + - Regtest 0fb882ff m/84'/1'/0' tpubDCfk37PqcQx6nFtFVuYHvRLJHxvYj33NjHkKRyRmWyCjyJ64sYBXyVjsTHaLBp5GLhM91VBgJ8nKDWDu52J2xVRy64c7ybEjjyWQJuQGLcg + - 1 Input + - 99,992,460 sats + - 2 Outputs + - 1 Output back to self (bcrt1qvwkhakqhz7m7kmz6332avatsmdy32m644g86vv) of 99,992,296 sats + - 1 OP_RETURN: "Chancellor on the brink of third bailout" + - Fee 164 sats + """ + psbt_base64 = "cHNidP8BAIYCAAAAATpQ10o+gKdZ8ThpKsbfHiHYn3NhvUrQ5DvW0ZWX8jKLAAAAAAD9////AujC9QUAAAAAFgAUY61+2BcXt+tsWoxV1nVw20kVb1UAAAAAAAAAACtqTChDaGFuY2VsbG9yIG9uIHRoZSBicmluayBvZiB0aGlyZCBiYWlsb3V0aQAAAE8BBDWHzwNXmUmVgAAAANRFa7R5gYD84Wbha3d1QnjgfYPOBw87on6cXS32WoyqAsPFtPxB7PRTdbujUnBPUVDh9YUBtwrl4nc0OcRNGvIyEA+4gv9UAACAAQAAgAAAAIAAAQB0AgAAAAGNFK/1X0fP5q+nu5XX7Tk2VRa0EL+jkGI9CHiJvsjZCgAAAAAA/f///wKMw/UFAAAAABYAFIpZMNnUU6cQt8Q0YpZ0pnvsSA5fAAAAAAAAAAAZakwWYml0Y29pbiBpcyBmcmVlIHNwZWVjaGgAAAABAR+Mw/UFAAAAABYAFIpZMNnUU6cQt8Q0YpZ0pnvsSA5fAQMEAQAAACIGAvxDI0eNI1oQ2AU69R7A0jf+hUdilWCgrWHgdzkqlaXMGA+4gv9UAACAAQAAgAAAAIAAAAAAAQAAAAAiAgK9qKtzGWyiRrpmupdA99NVLriz3GQy6cENbyD19sfl/hgPuIL/VAAAgAEAAIAAAACAAAAAAAIAAAAAAA==" + + raw = a2b_base64(psbt_base64) + tx = psbt.PSBT.parse(raw) + + mnemonic = "model ensure search plunge galaxy firm exclude brain satoshi meadow cable roast".split() + pw = "" + seed = Seed(mnemonic, passphrase=pw) + + psbt_parser = PSBTParser(p=tx, seed=seed, network=SettingsConstants.REGTEST) + + assert psbt_parser.op_return == "Chancellor on the brink of third bailout" + + # PSBT is an internal self-spend to the its own receive addr, but the parser categorizes it as "change" + assert psbt_parser.change_data == [ + { + 'output_index': 0, + 'address': 'bcrt1qvwkhakqhz7m7kmz6332avatsmdy32m644g86vv', + 'amount': 99992296, + 'fingerprint': ['0fb882ff'], + 'derivation_path': ["m/84h/1h/0h/0/2"]} + ] + assert psbt_parser.spend_amount == 0 # This is a self-spend; no value being spent, other than the tx fee + assert psbt_parser.change_amount == 99992296 + assert psbt_parser.destination_addresses == [] + assert psbt_parser.destination_amounts == [] From 2cd30a0e2cb3c40ff9563387917c7622c9d19f09 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Mon, 8 Jan 2024 15:05:51 -0600 Subject: [PATCH 05/15] Add flow-based test --- tests/test_flows_psbt.py | 234 +++++++++++++++++++++++++-------------- 1 file changed, 150 insertions(+), 84 deletions(-) diff --git a/tests/test_flows_psbt.py b/tests/test_flows_psbt.py index 3f487380..a71d4b83 100644 --- a/tests/test_flows_psbt.py +++ b/tests/test_flows_psbt.py @@ -3,90 +3,156 @@ from seedsigner.views.view import MainMenuView from seedsigner.views import scan_views, seed_views, psbt_views + + class TestPSBTFlows(FlowTest): - def test_scan_psbt_first_then_correct_seedqr_flow(self): - """ - Selecting "Scan" from the MainMenuView and scanning a PSBT should enter the PSBTSelectSeedView flow - when Scan a Seed is selected from PSBTSelectSeedView it should enter the ScanView flow - when a SeedQR is scanned it should enter the PSBTOverviewView flow - since the PSBT has change no warning is displayed and it should enter the PSBTMathView flow - since the PSBT is not a self transfer it should enter the PSBTAddressDetailsView flow - """ - def load_psbt_into_decoder(view: scan_views.ScanView): - """ - PSBT Tx and Wallet Details - - Single Sig Wallet P2WPKH (Native Segwit) with no passphrase - - Regtest c751dc07 m/84'/1'/0' tpubDDZBrnxMxbVzqt8EoEiABPxeKzFWma5pra5UEbg3Wst1hrwr6feuvcy7Sov7cpuYx94ypuy1PQ9NDNoQagFs37wGALzLb5Ei3FvyJWPPPKZ - - 2 Inputs - - 56,522,834 sats - - 1,990,245,069 sats - - 4 Outputs - - 1 Output to another wallet (bcrt1q7cw0wzy8g6mq5qvkpvhnk5gsps5ncy3srp0n2j) of 123,456 sats - - 3 Outputs change - - 3 outputs to emulate a fake mix to increase privacy - - Change addresses are index 1/7, 1/8, 1/9 - - 1/7 address bcrt1q53j0xwuskuf5gnvynadh0hlazyy8srydlucrhg with amount 123,456 sats - - 1/8 address bcrt1q5gtw3zfp4cx67yk5q42q6j6rfza8aqcwpyyslv with amount 1,990,121,477 sats - - 1/9 address bcrt1q9rrg7399m43cn0yg4tz0v0ate89jgf2d6kpz7v with amount 56,399,242 sats - - Fee 272 sats - """ - view.decoder.add_data("cHNidP8BANgCAAAAAsTXZs3fz/dmGb6M80+jjvJZdYya+cw5bT/dGuhZFdSlAAAAAAD9////qo6xg/UZAvUkcbse1F+C9zbP/FeZNjThx7SCIn6eMCgBAAAAAP3///8EQOIBAAAAAAAWABSkZPM7kLcTRE2En1t33/0RCHgMjQXYnnYAAAAAFgAUKMaPRKXdY4m8iKrE9j+rycskJU1A4gEAAAAAABYAFPYc9wiHRrYKAZYLLztREAwpPBIwipVcAwAAAAAWABSiFuiJIa4NrxLUBVQNS0NIun6DDtoRAABPAQQ1h88DBcQGZIAAAAA+0J+jlNL3dpWwlnBi8Dx+Ipg4e6uvB3HdjzFPX7r9CAOOlAIxgII+/xCcj+XoEenKH7wj5s5wlu7Q7CCZWFLGLhA5Su0UVAAAgAEAAIAAAACAAAEA7QIAAAAEE6njX/fnvn7hbkKIRcxzNYFOSfbCdNeWnd7Fe/1UcQ0BAAAAAP3///8TqeNf9+e+fuFuQohFzHM1gU5J9sJ015ad3sV7/VRxDQMAAAAA/f///xOp41/3575+4W5CiEXMczWBTkn2wnTXlp3exXv9VHENBAAAAAD9////E6njX/fnvn7hbkKIRcxzNYFOSfbCdNeWnd7Fe/1UcQ0GAAAAAP3///8CUnheAwAAAAAWABRCfygPJ+Fjsx4BknYvvm3A3qKn2xJ/XQcAAAAAF6kU1I4TAst5nAj15ey7vwe5cM3OFq+HlhEAAAEBH1J4XgMAAAAAFgAUQn8oDyfhY7MeAZJ2L75twN6ip9sBAwQBAAAAIgYCo7sfm78RQY3B5n0ac/QF8VtMAzFnci+h5D1MtpgRY7oYOUrtFFQAAIABAACAAAAAgAEAAAAGAAAAAAEAcQIAAAABxY7wh0nsfJQfzWrD/9rN9BYsM+iOmPaO6I0ANFgO/PcAAAAAAP3///8CptiUAAAAAAAWABRIm4HhQY/TzOjeWSPRrbuJo9MlW826oHYAAAAAFgAU0z+0L2QSLGtyQTn8FhbCpcI7jbliAQAAAQEfzbqgdgAAAAAWABTTP7QvZBIsa3JBOfwWFsKlwjuNuQEDBAEAAAAiBgITHmebEANk81CraV4xZIpqkNjjw0tIvezl1Ism1NRH3Rg5Su0UVAAAgAEAAIAAAACAAQAAAAAAAAAAIgICuTT7WnuiUTpObjWnZFHzIeEvW9PTB+1LLVFNQJVFeIIYOUrtFFQAAIABAACAAAAAgAEAAAAHAAAAACICAk8f3hpc5C35chgSg+Pe2zZ9IhHREd4aKW2+yAMRIFeqGDlK7RRUAACAAQAAgAAAAIABAAAACQAAAAAAIgIDjt1CjvrnMMnjbmTNKUAYoKEDRbmKjNjbq+6Ppqj3bqQYOUrtFFQAAIABAACAAAAAgAEAAAAIAAAAAA==") + def test_scan_psbt_first_then_correct_seedqr_flow(self): + """ + Selecting "Scan" from the MainMenuView and scanning a PSBT should enter the PSBTSelectSeedView flow + when Scan a Seed is selected from PSBTSelectSeedView it should enter the ScanView flow + when a SeedQR is scanned it should enter the PSBTOverviewView flow + since the PSBT has change no warning is displayed and it should enter the PSBTMathView flow + since the PSBT is not a self transfer it should enter the PSBTAddressDetailsView flow + """ + def load_psbt_into_decoder(view: scan_views.ScanView): + """ + PSBT Tx and Wallet Details + - Single Sig Wallet P2WPKH (Native Segwit) with no passphrase + - Regtest c751dc07 m/84'/1'/0' tpubDDZBrnxMxbVzqt8EoEiABPxeKzFWma5pra5UEbg3Wst1hrwr6feuvcy7Sov7cpuYx94ypuy1PQ9NDNoQagFs37wGALzLb5Ei3FvyJWPPPKZ + - 2 Inputs + - 56,522,834 sats + - 1,990,245,069 sats + - 4 Outputs + - 1 Output to another wallet (bcrt1q7cw0wzy8g6mq5qvkpvhnk5gsps5ncy3srp0n2j) of 123,456 sats + - 3 Outputs change + - 3 outputs to emulate a fake mix to increase privacy + - Change addresses are index 1/7, 1/8, 1/9 + - 1/7 address bcrt1q53j0xwuskuf5gnvynadh0hlazyy8srydlucrhg with amount 123,456 sats + - 1/8 address bcrt1q5gtw3zfp4cx67yk5q42q6j6rfza8aqcwpyyslv with amount 1,990,121,477 sats + - 1/9 address bcrt1q9rrg7399m43cn0yg4tz0v0ate89jgf2d6kpz7v with amount 56,399,242 sats + - Fee 272 sats + """ + view.decoder.add_data("cHNidP8BANgCAAAAAsTXZs3fz/dmGb6M80+jjvJZdYya+cw5bT/dGuhZFdSlAAAAAAD9////qo6xg/UZAvUkcbse1F+C9zbP/FeZNjThx7SCIn6eMCgBAAAAAP3///8EQOIBAAAAAAAWABSkZPM7kLcTRE2En1t33/0RCHgMjQXYnnYAAAAAFgAUKMaPRKXdY4m8iKrE9j+rycskJU1A4gEAAAAAABYAFPYc9wiHRrYKAZYLLztREAwpPBIwipVcAwAAAAAWABSiFuiJIa4NrxLUBVQNS0NIun6DDtoRAABPAQQ1h88DBcQGZIAAAAA+0J+jlNL3dpWwlnBi8Dx+Ipg4e6uvB3HdjzFPX7r9CAOOlAIxgII+/xCcj+XoEenKH7wj5s5wlu7Q7CCZWFLGLhA5Su0UVAAAgAEAAIAAAACAAAEA7QIAAAAEE6njX/fnvn7hbkKIRcxzNYFOSfbCdNeWnd7Fe/1UcQ0BAAAAAP3///8TqeNf9+e+fuFuQohFzHM1gU5J9sJ015ad3sV7/VRxDQMAAAAA/f///xOp41/3575+4W5CiEXMczWBTkn2wnTXlp3exXv9VHENBAAAAAD9////E6njX/fnvn7hbkKIRcxzNYFOSfbCdNeWnd7Fe/1UcQ0GAAAAAP3///8CUnheAwAAAAAWABRCfygPJ+Fjsx4BknYvvm3A3qKn2xJ/XQcAAAAAF6kU1I4TAst5nAj15ey7vwe5cM3OFq+HlhEAAAEBH1J4XgMAAAAAFgAUQn8oDyfhY7MeAZJ2L75twN6ip9sBAwQBAAAAIgYCo7sfm78RQY3B5n0ac/QF8VtMAzFnci+h5D1MtpgRY7oYOUrtFFQAAIABAACAAAAAgAEAAAAGAAAAAAEAcQIAAAABxY7wh0nsfJQfzWrD/9rN9BYsM+iOmPaO6I0ANFgO/PcAAAAAAP3///8CptiUAAAAAAAWABRIm4HhQY/TzOjeWSPRrbuJo9MlW826oHYAAAAAFgAU0z+0L2QSLGtyQTn8FhbCpcI7jbliAQAAAQEfzbqgdgAAAAAWABTTP7QvZBIsa3JBOfwWFsKlwjuNuQEDBAEAAAAiBgITHmebEANk81CraV4xZIpqkNjjw0tIvezl1Ism1NRH3Rg5Su0UVAAAgAEAAIAAAACAAQAAAAAAAAAAIgICuTT7WnuiUTpObjWnZFHzIeEvW9PTB+1LLVFNQJVFeIIYOUrtFFQAAIABAACAAAAAgAEAAAAHAAAAACICAk8f3hpc5C35chgSg+Pe2zZ9IhHREd4aKW2+yAMRIFeqGDlK7RRUAACAAQAAgAAAAIABAAAACQAAAAAAIgIDjt1CjvrnMMnjbmTNKUAYoKEDRbmKjNjbq+6Ppqj3bqQYOUrtFFQAAIABAACAAAAAgAEAAAAIAAAAAA==") + + def load_seed_into_decoder(view: scan_views.ScanView): + view.decoder.add_data("080115060387063104071857067618681125136207731354") + + self.run_sequence([ + FlowStep(MainMenuView, button_data_selection=MainMenuView.SCAN), + FlowStep(scan_views.ScanView, before_run=load_psbt_into_decoder), # simulate read PSBT; ret val is ignored + FlowStep(psbt_views.PSBTSelectSeedView, button_data_selection=psbt_views.PSBTSelectSeedView.SCAN_SEED), + FlowStep(scan_views.ScanSeedQRView, before_run=load_seed_into_decoder), + FlowStep(seed_views.SeedFinalizeView, button_data_selection=seed_views.SeedFinalizeView.FINALIZE), + FlowStep(seed_views.SeedOptionsView, is_redirect=True), + FlowStep(psbt_views.PSBTOverviewView), + FlowStep(psbt_views.PSBTMathView), + FlowStep(psbt_views.PSBTAddressDetailsView, button_data_selection=0), + FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), + FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), + FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), + FlowStep(psbt_views.PSBTFinalizeView, button_data_selection=psbt_views.PSBTFinalizeView.APPROVE_PSBT), + FlowStep(psbt_views.PSBTSignedQRDisplayView), + FlowStep(MainMenuView) + ]) + + + def test_scan_multisig_psbt_seed_already_signed_flow(self): + + def load_psbt_into_decoder(view: scan_views.ScanView): + view.decoder.add_data("cHNidP8BAIkCAAAAAc9dCSh2RcRPfHaT5bNVBpbg0jAekRLqOK+bpN/QA0jeAAAAAAD9////AtAHAAAAAAAAIlEg24shYsV3IRCzlgmMKjAsR4Ad9tX896z7zDAi5q0TU9H3CgAAAAAAACIAIByGQg/VP2aRID62ty40E64HYZeRRsKRGLt8J/76R6stQ04FAE8BBDWHzwSLLGdzgAAAAq3q6nR20JnHR+vKrBQdWxN9C7xU8zNX942mVF7AQpl2ArrdLwVlkGxaatQJ4wwkvypNBKbwOq9hXGLNlKi7rZWAFDUxzXUwAACAAQAAgAAAAIACAACATwEENYfPBHOCZmWAAAACmH6KTXIny0vueRgQFBq4M6oMuG8f1QM0I/RzKQ03bCgCHrF0fyUtV0+FD2N34u/woqb8MAt/o+7Ed58RddhY8zYUCUjSaDAAAIABAACAAAAAgAIAAIAAAQEriBMAAAAAAAAiACBY4WsjDgJXLj3VW222jU1tkIIhT26ce/2efH73BWGGBiICAqyfkrdUO662QBrdvJcSOZMFxniD7M1awm9U0Kb5XCm5RzBEAiAPkQTY84YjFFkpD6MI2cc5rJySqws5fsTQA/8XEZFpbAIgTNVykbEH4Z7bqyzhhy6lty0K8rtCUDCaHNv+47NNIWgBAQMEAQAAAAEFR1IhApL4XO+VE1pPYn5wnRFyJQKVSc9TX2dO6KIBH6jwvgPaIQKsn5K3VDuutkAa3byXEjmTBcZ4g+zNWsJvVNCm+VwpuVKuIgYCkvhc75UTWk9ifnCdEXIlApVJz1NfZ07oogEfqPC+A9ocNTHNdTAAAIABAACAAAAAgAIAAIAAAAAAAAAAACIGAqyfkrdUO662QBrdvJcSOZMFxniD7M1awm9U0Kb5XCm5HAlI0mgwAACAAQAAgAAAAIACAACAAAAAAAAAAAAAAAEBR1IhApYXaczuYbBM/A+EH639Ir2yIB4PxL46dK/I1V1O9aHgIQLa02HCI/+EP+9gGpxHskjYWFN5hZzXY7RRvwV4UF42ylKuIgIClhdpzO5hsEz8D4Qfrf0ivbIgHg/Evjp0r8jVXU71oeAcNTHNdTAAAIABAACAAAAAgAIAAIABAAAAAAAAACICAtrTYcIj/4Q/72AanEeySNhYU3mFnNdjtFG/BXhQXjbKHAlI0mgwAACAAQAAgAAAAIACAACAAQAAAAAAAAAA") + + def load_seed_into_decoder(view: scan_views.ScanView): + view.decoder.add_data("073318950739065415961602009907670428187212261116") + + self.run_sequence([ + FlowStep(MainMenuView, button_data_selection=MainMenuView.SCAN), + FlowStep(scan_views.ScanView, before_run=load_psbt_into_decoder), # simulate read PSBT; ret val is ignored + FlowStep(psbt_views.PSBTSelectSeedView, button_data_selection=psbt_views.PSBTSelectSeedView.SCAN_SEED), + FlowStep(scan_views.ScanSeedQRView, before_run=load_seed_into_decoder), + FlowStep(seed_views.SeedFinalizeView, button_data_selection=seed_views.SeedFinalizeView.FINALIZE), + FlowStep(seed_views.SeedOptionsView, is_redirect=True), + FlowStep(psbt_views.PSBTOverviewView), + FlowStep(psbt_views.PSBTMathView), + FlowStep(psbt_views.PSBTAddressDetailsView, button_data_selection=0), + FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.SKIP_VERIFICATION), + FlowStep(psbt_views.PSBTFinalizeView, button_data_selection=psbt_views.PSBTFinalizeView.APPROVE_PSBT), + FlowStep(psbt_views.PSBTSigningErrorView, button_data_selection=psbt_views.PSBTSigningErrorView.SELECT_DIFF_SEED), + FlowStep(psbt_views.PSBTSelectSeedView, button_data_selection=psbt_views.PSBTSelectSeedView.SCAN_SEED), + FlowStep(scan_views.ScanSeedQRView, before_run=load_seed_into_decoder), + FlowStep(seed_views.SeedFinalizeView, button_data_selection=seed_views.SeedFinalizeView.PASSPHRASE), + FlowStep(seed_views.SeedAddPassphraseView, screen_return_value="abc"), + FlowStep(seed_views.SeedReviewPassphraseView, button_data_selection=seed_views.SeedReviewPassphraseView.DONE), + FlowStep(seed_views.SeedOptionsView, is_redirect=True), + FlowStep(psbt_views.PSBTOverviewView), + FlowStep(psbt_views.PSBTMathView), + FlowStep(psbt_views.PSBTAddressDetailsView, button_data_selection=0), + FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.SKIP_VERIFICATION), + FlowStep(psbt_views.PSBTFinalizeView, button_data_selection=psbt_views.PSBTFinalizeView.APPROVE_PSBT), + FlowStep(psbt_views.PSBTSignedQRDisplayView), + FlowStep(MainMenuView), + ]) + + def load_seed_into_decoder(view: scan_views.ScanView): + view.decoder.add_data("080115060387063104071857067618681125136207731354") + + self.run_sequence([ + FlowStep(MainMenuView, button_data_selection=MainMenuView.SCAN), + FlowStep(scan_views.ScanView, before_run=load_psbt_into_decoder), # simulate read PSBT; ret val is ignored + FlowStep(psbt_views.PSBTSelectSeedView, button_data_selection=psbt_views.PSBTSelectSeedView.SCAN_SEED), + FlowStep(scan_views.ScanSeedQRView, before_run=load_seed_into_decoder), + FlowStep(seed_views.SeedFinalizeView, button_data_selection=seed_views.SeedFinalizeView.FINALIZE), + FlowStep(seed_views.SeedOptionsView, is_redirect=True), + FlowStep(psbt_views.PSBTOverviewView), + FlowStep(psbt_views.PSBTMathView), + FlowStep(psbt_views.PSBTAddressDetailsView, button_data_selection=0), + FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), + FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), + FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), + FlowStep(psbt_views.PSBTFinalizeView, button_data_selection=psbt_views.PSBTFinalizeView.APPROVE_PSBT), + FlowStep(psbt_views.PSBTSignedQRDisplayView), + FlowStep(MainMenuView) + ]) + + + def test_parse_and_display_op_return_content(self): + """ + PSBTs that include an OP_RETURN should be able to be parsed like any other + PSBT and route to the dedicated OP_RETURN View to display the content + """ + def load_psbt_into_decoder(view: scan_views.ScanView): + """ + PSBT Tx and Wallet Details + - Single Sig Wallet P2WPKH (Native Segwit) with no passphrase + - Regtest 0fb882ff m/84'/1'/0' tpubDCfk37PqcQx6nFtFVuYHvRLJHxvYj33NjHkKRyRmWyCjyJ64sYBXyVjsTHaLBp5GLhM91VBgJ8nKDWDu52J2xVRy64c7ybEjjyWQJuQGLcg + - 1 Input + - 99,992,460 sats + - 2 Outputs + - 1 Output back to self (bcrt1qvwkhakqhz7m7kmz6332avatsmdy32m644g86vv) of 99,992,296 sats + - 1 OP_RETURN: "Chancellor on the brink of third bailout" + - Fee 164 sats + """ + view.decoder.add_data("cHNidP8BAIYCAAAAATpQ10o+gKdZ8ThpKsbfHiHYn3NhvUrQ5DvW0ZWX8jKLAAAAAAD9////AujC9QUAAAAAFgAUY61+2BcXt+tsWoxV1nVw20kVb1UAAAAAAAAAACtqTChDaGFuY2VsbG9yIG9uIHRoZSBicmluayBvZiB0aGlyZCBiYWlsb3V0aQAAAE8BBDWHzwNXmUmVgAAAANRFa7R5gYD84Wbha3d1QnjgfYPOBw87on6cXS32WoyqAsPFtPxB7PRTdbujUnBPUVDh9YUBtwrl4nc0OcRNGvIyEA+4gv9UAACAAQAAgAAAAIAAAQB0AgAAAAGNFK/1X0fP5q+nu5XX7Tk2VRa0EL+jkGI9CHiJvsjZCgAAAAAA/f///wKMw/UFAAAAABYAFIpZMNnUU6cQt8Q0YpZ0pnvsSA5fAAAAAAAAAAAZakwWYml0Y29pbiBpcyBmcmVlIHNwZWVjaGgAAAABAR+Mw/UFAAAAABYAFIpZMNnUU6cQt8Q0YpZ0pnvsSA5fAQMEAQAAACIGAvxDI0eNI1oQ2AU69R7A0jf+hUdilWCgrWHgdzkqlaXMGA+4gv9UAACAAQAAgAAAAIAAAAAAAQAAAAAiAgK9qKtzGWyiRrpmupdA99NVLriz3GQy6cENbyD19sfl/hgPuIL/VAAAgAEAAIAAAACAAAAAAAIAAAAAAA==") + + def load_seed_into_decoder(view: scan_views.ScanView): + view.decoder.add_data("114006021552133507590698063102151531110102551496") + + self.run_sequence([ + FlowStep(MainMenuView, button_data_selection=MainMenuView.SCAN), + FlowStep(scan_views.ScanView, before_run=load_psbt_into_decoder), # simulate read PSBT; ret val is ignored + FlowStep(psbt_views.PSBTSelectSeedView, button_data_selection=psbt_views.PSBTSelectSeedView.SCAN_SEED), + FlowStep(scan_views.ScanSeedQRView, before_run=load_seed_into_decoder), + FlowStep(seed_views.SeedFinalizeView, button_data_selection=seed_views.SeedFinalizeView.FINALIZE), + FlowStep(seed_views.SeedOptionsView, is_redirect=True), + FlowStep(psbt_views.PSBTOverviewView), + FlowStep(psbt_views.PSBTMathView), + FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), + + # Should route to display OP_RETURN content + FlowStep(psbt_views.PSBTOpReturnView, button_data_selection=0), - def load_seed_into_decoder(view: scan_views.ScanView): - view.decoder.add_data("080115060387063104071857067618681125136207731354") - - self.run_sequence([ - FlowStep(MainMenuView, button_data_selection=MainMenuView.SCAN), - FlowStep(scan_views.ScanView, before_run=load_psbt_into_decoder), # simulate read PSBT; ret val is ignored - FlowStep(psbt_views.PSBTSelectSeedView, button_data_selection=psbt_views.PSBTSelectSeedView.SCAN_SEED), - FlowStep(scan_views.ScanSeedQRView, before_run=load_seed_into_decoder), - FlowStep(seed_views.SeedFinalizeView, button_data_selection=seed_views.SeedFinalizeView.FINALIZE), - FlowStep(seed_views.SeedOptionsView, is_redirect=True), - FlowStep(psbt_views.PSBTOverviewView), - FlowStep(psbt_views.PSBTMathView), - FlowStep(psbt_views.PSBTAddressDetailsView, button_data_selection=0), - FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), - FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), - FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), - FlowStep(psbt_views.PSBTFinalizeView, button_data_selection=psbt_views.PSBTFinalizeView.APPROVE_PSBT), - FlowStep(psbt_views.PSBTSignedQRDisplayView), - FlowStep(MainMenuView) - ]) - - def test_scan_multisig_psbt_seed_already_signed_flow(self): - - def load_psbt_into_decoder(view: scan_views.ScanView): - view.decoder.add_data("cHNidP8BAIkCAAAAAc9dCSh2RcRPfHaT5bNVBpbg0jAekRLqOK+bpN/QA0jeAAAAAAD9////AtAHAAAAAAAAIlEg24shYsV3IRCzlgmMKjAsR4Ad9tX896z7zDAi5q0TU9H3CgAAAAAAACIAIByGQg/VP2aRID62ty40E64HYZeRRsKRGLt8J/76R6stQ04FAE8BBDWHzwSLLGdzgAAAAq3q6nR20JnHR+vKrBQdWxN9C7xU8zNX942mVF7AQpl2ArrdLwVlkGxaatQJ4wwkvypNBKbwOq9hXGLNlKi7rZWAFDUxzXUwAACAAQAAgAAAAIACAACATwEENYfPBHOCZmWAAAACmH6KTXIny0vueRgQFBq4M6oMuG8f1QM0I/RzKQ03bCgCHrF0fyUtV0+FD2N34u/woqb8MAt/o+7Ed58RddhY8zYUCUjSaDAAAIABAACAAAAAgAIAAIAAAQEriBMAAAAAAAAiACBY4WsjDgJXLj3VW222jU1tkIIhT26ce/2efH73BWGGBiICAqyfkrdUO662QBrdvJcSOZMFxniD7M1awm9U0Kb5XCm5RzBEAiAPkQTY84YjFFkpD6MI2cc5rJySqws5fsTQA/8XEZFpbAIgTNVykbEH4Z7bqyzhhy6lty0K8rtCUDCaHNv+47NNIWgBAQMEAQAAAAEFR1IhApL4XO+VE1pPYn5wnRFyJQKVSc9TX2dO6KIBH6jwvgPaIQKsn5K3VDuutkAa3byXEjmTBcZ4g+zNWsJvVNCm+VwpuVKuIgYCkvhc75UTWk9ifnCdEXIlApVJz1NfZ07oogEfqPC+A9ocNTHNdTAAAIABAACAAAAAgAIAAIAAAAAAAAAAACIGAqyfkrdUO662QBrdvJcSOZMFxniD7M1awm9U0Kb5XCm5HAlI0mgwAACAAQAAgAAAAIACAACAAAAAAAAAAAAAAAEBR1IhApYXaczuYbBM/A+EH639Ir2yIB4PxL46dK/I1V1O9aHgIQLa02HCI/+EP+9gGpxHskjYWFN5hZzXY7RRvwV4UF42ylKuIgIClhdpzO5hsEz8D4Qfrf0ivbIgHg/Evjp0r8jVXU71oeAcNTHNdTAAAIABAACAAAAAgAIAAIABAAAAAAAAACICAtrTYcIj/4Q/72AanEeySNhYU3mFnNdjtFG/BXhQXjbKHAlI0mgwAACAAQAAgAAAAIACAACAAQAAAAAAAAAA") - - def load_seed_into_decoder(view: scan_views.ScanView): - view.decoder.add_data("073318950739065415961602009907670428187212261116") - - self.run_sequence([ - FlowStep(MainMenuView, button_data_selection=MainMenuView.SCAN), - FlowStep(scan_views.ScanView, before_run=load_psbt_into_decoder), # simulate read PSBT; ret val is ignored - FlowStep(psbt_views.PSBTSelectSeedView, button_data_selection=psbt_views.PSBTSelectSeedView.SCAN_SEED), - FlowStep(scan_views.ScanSeedQRView, before_run=load_seed_into_decoder), - FlowStep(seed_views.SeedFinalizeView, button_data_selection=seed_views.SeedFinalizeView.FINALIZE), - FlowStep(seed_views.SeedOptionsView, is_redirect=True), - FlowStep(psbt_views.PSBTOverviewView), - FlowStep(psbt_views.PSBTMathView), - FlowStep(psbt_views.PSBTAddressDetailsView, button_data_selection=0), - FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.SKIP_VERIFICATION), - FlowStep(psbt_views.PSBTFinalizeView, button_data_selection=psbt_views.PSBTFinalizeView.APPROVE_PSBT), - FlowStep(psbt_views.PSBTSigningErrorView, button_data_selection=psbt_views.PSBTSigningErrorView.SELECT_DIFF_SEED), - FlowStep(psbt_views.PSBTSelectSeedView, button_data_selection=psbt_views.PSBTSelectSeedView.SCAN_SEED), - FlowStep(scan_views.ScanSeedQRView, before_run=load_seed_into_decoder), - FlowStep(seed_views.SeedFinalizeView, button_data_selection=seed_views.SeedFinalizeView.PASSPHRASE), - FlowStep(seed_views.SeedAddPassphraseView, screen_return_value="abc"), - FlowStep(seed_views.SeedReviewPassphraseView, button_data_selection=seed_views.SeedReviewPassphraseView.DONE), - FlowStep(seed_views.SeedOptionsView, is_redirect=True), - FlowStep(psbt_views.PSBTOverviewView), - FlowStep(psbt_views.PSBTMathView), - FlowStep(psbt_views.PSBTAddressDetailsView, button_data_selection=0), - FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.SKIP_VERIFICATION), - FlowStep(psbt_views.PSBTFinalizeView, button_data_selection=psbt_views.PSBTFinalizeView.APPROVE_PSBT), - FlowStep(psbt_views.PSBTSignedQRDisplayView), - FlowStep(MainMenuView), - ]) - + # Should be able to sign the psbt + FlowStep(psbt_views.PSBTFinalizeView, button_data_selection=psbt_views.PSBTFinalizeView.APPROVE_PSBT), + FlowStep(psbt_views.PSBTSignedQRDisplayView), + FlowStep(MainMenuView) + ]) From 55ccde59341e47d9858b2d7b7087e6b0ec492b7f Mon Sep 17 00:00:00 2001 From: kdmukai Date: Mon, 8 Jan 2024 15:05:59 -0600 Subject: [PATCH 06/15] Add screenshots --- src/seedsigner/gui/screens/psbt_screens.py | 19 +++--- tests/screenshot_generator/generator.py | 72 +++++++++++++++++++--- tests/test_psbt_parser.py | 2 +- 3 files changed, 74 insertions(+), 19 deletions(-) diff --git a/src/seedsigner/gui/screens/psbt_screens.py b/src/seedsigner/gui/screens/psbt_screens.py index de74d6ba..8f32eaee 100644 --- a/src/seedsigner/gui/screens/psbt_screens.py +++ b/src/seedsigner/gui/screens/psbt_screens.py @@ -696,13 +696,11 @@ def __post_init__(self): super().__post_init__() - font_size = GUIConstants.TOP_NAV_TITLE_FONT_SIZE + 2 - try: # Simple case: display human-readable text self.components.append(TextArea( text=self.op_return.decode(errors="strict"), - font_size=font_size, + font_size=GUIConstants.TOP_NAV_TITLE_FONT_SIZE, is_text_centered=True, allow_text_overflow=True, screen_y=self.top_nav.height + GUIConstants.COMPONENT_PADDING, @@ -712,8 +710,11 @@ def __post_init__(self): except UnicodeDecodeError: # Contains data that can't be converted to UTF-8; probably encoded and not # meant to be human readable. - chars_per_line = 16 - decoded_str = self.op_return.decode(errors="ignore") + font = Fonts.get_font(GUIConstants.FIXED_WIDTH_FONT_NAME, size=GUIConstants.BODY_FONT_SIZE) + (left, top, right, bottom) = font.getbbox("X", anchor="ls") + chars_per_line = int((self.canvas_width - 2*GUIConstants.EDGE_PADDING) / (right - left)) + decoded_str = self.op_return.hex() + # decoded_str = self.op_return.decode(errors="ignore") num_lines = math.ceil(len(decoded_str) / chars_per_line) text = "" for i in range(num_lines): @@ -721,17 +722,17 @@ def __post_init__(self): text = text[:-1] label = TextArea( - text="Encoded bytes", + text="raw hex", font_color=GUIConstants.LABEL_FONT_COLOR, font_size=GUIConstants.LABEL_FONT_SIZE, - screen_y=self.top_nav.height + GUIConstants.COMPONENT_PADDING * 2, + screen_y=self.top_nav.height, ) self.components.append(label) self.components.append(TextArea( text=text, - # font_name=GUIConstants.FIXED_WIDTH_FONT_NAME, - font_size=font_size, + font_name=GUIConstants.FIXED_WIDTH_FONT_NAME, + font_size=GUIConstants.BODY_FONT_SIZE, screen_y=label.screen_y + label.height + GUIConstants.COMPONENT_PADDING, )) diff --git a/tests/screenshot_generator/generator.py b/tests/screenshot_generator/generator.py index 278df338..9efd2546 100644 --- a/tests/screenshot_generator/generator.py +++ b/tests/screenshot_generator/generator.py @@ -1,11 +1,17 @@ import embit import os +import random import sys import time from unittest.mock import Mock, patch, MagicMock from seedsigner.helpers import embit_utils -from seedsigner.models.settings import Settings +from embit import compact +from embit.psbt import PSBT, OutputScope +from embit.script import Script + +from seedsigner.helpers import embit_utils +from seedsigner.models.psbt_parser import OPCODES, PSBTParser # Prevent importing modules w/Raspi hardware dependencies. @@ -22,8 +28,6 @@ from seedsigner.controller import Controller from seedsigner.gui.renderer import Renderer from seedsigner.gui.toast import BaseToastOverlayManagerThread, RemoveSDCardToastManagerThread, SDCardStateChangeToastManagerThread -from seedsigner.hardware.buttons import HardwareButtons -from seedsigner.hardware.camera import Camera from seedsigner.hardware.microsd import MicroSD from seedsigner.models.decode_qr import DecodeQR from seedsigner.models.qr_type import QRType @@ -86,6 +90,34 @@ def test_generate_screenshots(target_locale): controller.psbt = decoder.get_psbt() controller.psbt_seed = seed_12b + def add_op_return_to_psbt(psbt: PSBT, raw_payload_data: bytes): + data = (compact.to_bytes(OPCODES.OP_RETURN) + + compact.to_bytes(OPCODES.OP_PUSHDATA1) + + compact.to_bytes(len(raw_payload_data)) + + raw_payload_data) + script = Script(data) + output = OutputScope() + output.script_pubkey = script + output.value = 0 + psbt.outputs.append(output) + return psbt.to_string() + + # Prep a PSBT with a human-readable OP_RETURN + raw_payload_data = "Chancellor on the brink of third bailout for banks".encode() + psbt = PSBT.from_base64(BASE64_PSBT_1) + + # Simplify the output side + output = psbt.outputs[-1] + psbt.outputs.clear() + psbt.outputs.append(output) + assert len(psbt.outputs) == 1 + BASE64_PSBT_WITH_OP_RETURN_TEXT = add_op_return_to_psbt(psbt, raw_payload_data) + + # Prep a PSBT with a (repeatably) random 80-byte OP_RETURN + random.seed(6102) + BASE64_PSBT_WITH_OP_RETURN_RAW_BYTES = add_op_return_to_psbt(PSBT.from_base64(BASE64_PSBT_1), random.randbytes(80)) + + # Multisig wallet descriptor for the multisig in the above PSBT MULTISIG_WALLET_DESCRIPTOR = """wsh(sortedmulti(1,[22bde1a9/48h/1h/0h/2h]tpubDFfsBrmpj226ZYiRszYi2qK6iGvh2vkkghfGB2YiRUVY4rqqedHCFEgw12FwDkm7rUoVtq9wLTKc6BN2sxswvQeQgp7m8st4FP8WtP8go76/{0,1}/*,[73c5da0a/48h/1h/0h/2h]tpubDFH9dgzveyD8zTbPUFuLrGmCydNvxehyNdUXKJAQN8x4aZ4j6UZqGfnqFrD4NqyaTVGKbvEW54tsvPTK2UoSbCC1PJY8iCNiwTL3RWZEheQ/{0,1}/*))#3jhtf6yx""" controller.multisig_wallet_descriptor = embit.descriptor.Descriptor.from_string(MULTISIG_WALLET_DESCRIPTOR) @@ -193,6 +225,11 @@ def test_generate_screenshots(target_locale): (NotYetImplementedView, {}, "PSBTChangeDetailsView_multisig_unverified"), # Must manually re-run this below (psbt_views.PSBTChangeDetailsView, dict(change_address_num=0), "PSBTChangeDetailsView_multisig_verified"), + + (NotYetImplementedView, {}, "PSBTOverviewView_op_return"), # Placeholder + (NotYetImplementedView, {}, "PSBTOpReturnView_text"), # Placeholder + (NotYetImplementedView, {}, "PSBTOpReturnView_raw_bytes"), # Placeholder + (psbt_views.PSBTAddressVerificationFailedView, dict(is_change=True, is_multisig=False), "PSBTAddressVerificationFailedView_singlesig_change"), (psbt_views.PSBTAddressVerificationFailedView, dict(is_change=False, is_multisig=False), "PSBTAddressVerificationFailedView_singlesig_selftransfer"), (psbt_views.PSBTAddressVerificationFailedView, dict(is_change=True, is_multisig=True), "PSBTAddressVerificationFailedView_multisig_change"), @@ -241,7 +278,9 @@ def test_generate_screenshots(target_locale): readme = f"""# SeedSigner Screenshots\n""" - def screencap_view(view_cls: View, view_name: str, view_args: dict={}, toast_thread: BaseToastOverlayManagerThread = None): + def screencap_view(view_cls: View, view_args: dict = {}, view_name: str = None, toast_thread: BaseToastOverlayManagerThread = None): + if not view_name: + view_name = view_cls.__name__ screenshot_renderer.set_screenshot_filename(f"{view_name}.png") try: print(f"Running {view_name}") @@ -266,7 +305,6 @@ def screencap_view(view_cls: View, view_name: str, view_args: dict={}, toast_thr if toast_thread: toast_thread.stop() - for section_name, screenshot_list in screenshot_sections.items(): subdir = section_name.lower().replace(" ", "_") screenshot_renderer.set_screenshot_path(os.path.join(screenshot_root, subdir)) @@ -289,25 +327,41 @@ def screencap_view(view_cls: View, view_name: str, view_args: dict={}, toast_thr view_name = view_cls.__name__ toast_thread = None - screencap_view(view_cls, view_name, view_args, toast_thread=toast_thread) + screencap_view(view_cls, view_args=view_args, view_name=view_name, toast_thread=toast_thread) readme += """ """ readme += f"""""" readme += """
{view_name}

\n""" readme += "" - # many screens don't work, leaving a missing image, re-run here for now + # Re-render some screens that require more manual intervention / setup than the above + # scripting can support. screenshot_renderer.set_screenshot_path(os.path.join(screenshot_root, "psbt_views")) + # Render the PSBTChangeDetailsView_multisig_unverified screenshot decoder = DecodeQR() decoder.add_data(BASE64_PSBT_1) controller.psbt = decoder.get_psbt() controller.psbt_seed = seed_12b controller.multisig_wallet_descriptor = None - screencap_view(psbt_views.PSBTChangeDetailsView, 'PSBTChangeDetailsView_multisig_unverified', dict(change_address_num=0)) + screencap_view(psbt_views.PSBTChangeDetailsView, view_name='PSBTChangeDetailsView_multisig_unverified', view_args=dict(change_address_num=0)) controller.psbt_seed = None - screencap_view(psbt_views.PSBTSelectSeedView, 'PSBTSelectSeedView', {}) + screencap_view(psbt_views.PSBTSelectSeedView, view_name='PSBTSelectSeedView') + + # Render OP_RETURN screens for real + controller.psbt_seed = seed_12b + decoder = DecodeQR() + decoder.add_data(BASE64_PSBT_WITH_OP_RETURN_TEXT) + controller.psbt = decoder.get_psbt() + controller.psbt_parser = PSBTParser(p=controller.psbt, seed=seed_12b) + screencap_view(psbt_views.PSBTOverviewView, view_name='PSBTOverviewView_op_return') + screencap_view(psbt_views.PSBTOpReturnView, view_name="PSBTOpReturnView_text") + + decoder.add_data(BASE64_PSBT_WITH_OP_RETURN_RAW_BYTES) + controller.psbt = decoder.get_psbt() + controller.psbt_parser = PSBTParser(p=controller.psbt, seed=seed_12b) + screencap_view(psbt_views.PSBTOpReturnView, view_name="PSBTOpReturnView_raw_bytes") with open(os.path.join(screenshot_root, "README.md"), 'w') as readme_file: readme_file.write(readme) diff --git a/tests/test_psbt_parser.py b/tests/test_psbt_parser.py index 90175ce6..49546285 100644 --- a/tests/test_psbt_parser.py +++ b/tests/test_psbt_parser.py @@ -139,7 +139,7 @@ def test_parse_op_return_content(): psbt_parser = PSBTParser(p=tx, seed=seed, network=SettingsConstants.REGTEST) - assert psbt_parser.op_return == "Chancellor on the brink of third bailout" + assert psbt_parser.op_return.decode() == "Chancellor on the brink of third bailout" # PSBT is an internal self-spend to the its own receive addr, but the parser categorizes it as "change" assert psbt_parser.change_data == [ From dcef5190b32ffcc74f060c49418358dc75285b22 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Mon, 8 Jan 2024 15:15:57 -0600 Subject: [PATCH 07/15] test bugfix: bytes not str --- tests/test_psbt_parser.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_psbt_parser.py b/tests/test_psbt_parser.py index 49546285..801a52d4 100644 --- a/tests/test_psbt_parser.py +++ b/tests/test_psbt_parser.py @@ -139,7 +139,8 @@ def test_parse_op_return_content(): psbt_parser = PSBTParser(p=tx, seed=seed, network=SettingsConstants.REGTEST) - assert psbt_parser.op_return.decode() == "Chancellor on the brink of third bailout" + # Remember to do the comparison as bytes + assert psbt_parser.op_return == "Chancellor on the brink of third bailout".encode() # PSBT is an internal self-spend to the its own receive addr, but the parser categorizes it as "change" assert psbt_parser.change_data == [ From 8c0d355dfcd44a7297cf422f78e005defdb45b94 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Mon, 8 Jan 2024 15:30:58 -0600 Subject: [PATCH 08/15] Minor cleanup; light comment updates --- src/seedsigner/gui/screens/psbt_screens.py | 1 - tests/screenshot_generator/generator.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/seedsigner/gui/screens/psbt_screens.py b/src/seedsigner/gui/screens/psbt_screens.py index 8f32eaee..a1f64a15 100644 --- a/src/seedsigner/gui/screens/psbt_screens.py +++ b/src/seedsigner/gui/screens/psbt_screens.py @@ -714,7 +714,6 @@ def __post_init__(self): (left, top, right, bottom) = font.getbbox("X", anchor="ls") chars_per_line = int((self.canvas_width - 2*GUIConstants.EDGE_PADDING) / (right - left)) decoded_str = self.op_return.hex() - # decoded_str = self.op_return.decode(errors="ignore") num_lines = math.ceil(len(decoded_str) / chars_per_line) text = "" for i in range(num_lines): diff --git a/tests/screenshot_generator/generator.py b/tests/screenshot_generator/generator.py index 9efd2546..53c5bbbf 100644 --- a/tests/screenshot_generator/generator.py +++ b/tests/screenshot_generator/generator.py @@ -227,7 +227,7 @@ def add_op_return_to_psbt(psbt: PSBT, raw_payload_data: bytes): (psbt_views.PSBTChangeDetailsView, dict(change_address_num=0), "PSBTChangeDetailsView_multisig_verified"), (NotYetImplementedView, {}, "PSBTOverviewView_op_return"), # Placeholder - (NotYetImplementedView, {}, "PSBTOpReturnView_text"), # Placeholder + (NotYetImplementedView, {}, "PSBTOpReturnView_text"), # Placeholder (NotYetImplementedView, {}, "PSBTOpReturnView_raw_bytes"), # Placeholder (psbt_views.PSBTAddressVerificationFailedView, dict(is_change=True, is_multisig=False), "PSBTAddressVerificationFailedView_singlesig_change"), From 0ca90053822675c6bab70d76e46e1bb92b860223 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Mon, 8 Jan 2024 15:45:03 -0600 Subject: [PATCH 09/15] Update the label when the data can't be decoded --- src/seedsigner/gui/screens/psbt_screens.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/seedsigner/gui/screens/psbt_screens.py b/src/seedsigner/gui/screens/psbt_screens.py index a1f64a15..369e93af 100644 --- a/src/seedsigner/gui/screens/psbt_screens.py +++ b/src/seedsigner/gui/screens/psbt_screens.py @@ -721,7 +721,7 @@ def __post_init__(self): text = text[:-1] label = TextArea( - text="raw hex", + text="raw hex data", font_color=GUIConstants.LABEL_FONT_COLOR, font_size=GUIConstants.LABEL_FONT_SIZE, screen_y=self.top_nav.height, From 03ebf7f2413484561f4541902c4f119630497989 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Mon, 4 Mar 2024 12:59:38 -0600 Subject: [PATCH 10/15] Rename screenshot image --- tests/screenshot_generator/generator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/screenshot_generator/generator.py b/tests/screenshot_generator/generator.py index 53c5bbbf..a0160bed 100644 --- a/tests/screenshot_generator/generator.py +++ b/tests/screenshot_generator/generator.py @@ -228,7 +228,7 @@ def add_op_return_to_psbt(psbt: PSBT, raw_payload_data: bytes): (NotYetImplementedView, {}, "PSBTOverviewView_op_return"), # Placeholder (NotYetImplementedView, {}, "PSBTOpReturnView_text"), # Placeholder - (NotYetImplementedView, {}, "PSBTOpReturnView_raw_bytes"), # Placeholder + (NotYetImplementedView, {}, "PSBTOpReturnView_raw_hex_data"), # Placeholder (psbt_views.PSBTAddressVerificationFailedView, dict(is_change=True, is_multisig=False), "PSBTAddressVerificationFailedView_singlesig_change"), (psbt_views.PSBTAddressVerificationFailedView, dict(is_change=False, is_multisig=False), "PSBTAddressVerificationFailedView_singlesig_selftransfer"), @@ -361,7 +361,7 @@ def screencap_view(view_cls: View, view_args: dict = {}, view_name: str = None, decoder.add_data(BASE64_PSBT_WITH_OP_RETURN_RAW_BYTES) controller.psbt = decoder.get_psbt() controller.psbt_parser = PSBTParser(p=controller.psbt, seed=seed_12b) - screencap_view(psbt_views.PSBTOpReturnView, view_name="PSBTOpReturnView_raw_bytes") + screencap_view(psbt_views.PSBTOpReturnView, view_name="PSBTOpReturnView_raw_hex_data") with open(os.path.join(screenshot_root, "README.md"), 'w') as readme_file: readme_file.write(readme) From 7c61492f5b6ea226a9998f2a2ae3856362ab9614 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Tue, 9 Jul 2024 08:02:42 -0500 Subject: [PATCH 11/15] temp change to tab spacing --- tests/test_flows_psbt.py | 280 +++++++++++++++++++-------------------- 1 file changed, 140 insertions(+), 140 deletions(-) diff --git a/tests/test_flows_psbt.py b/tests/test_flows_psbt.py index a71d4b83..b81cfe9e 100644 --- a/tests/test_flows_psbt.py +++ b/tests/test_flows_psbt.py @@ -7,152 +7,152 @@ class TestPSBTFlows(FlowTest): - def test_scan_psbt_first_then_correct_seedqr_flow(self): - """ - Selecting "Scan" from the MainMenuView and scanning a PSBT should enter the PSBTSelectSeedView flow - when Scan a Seed is selected from PSBTSelectSeedView it should enter the ScanView flow - when a SeedQR is scanned it should enter the PSBTOverviewView flow - since the PSBT has change no warning is displayed and it should enter the PSBTMathView flow - since the PSBT is not a self transfer it should enter the PSBTAddressDetailsView flow - """ - def load_psbt_into_decoder(view: scan_views.ScanView): - """ - PSBT Tx and Wallet Details - - Single Sig Wallet P2WPKH (Native Segwit) with no passphrase - - Regtest c751dc07 m/84'/1'/0' tpubDDZBrnxMxbVzqt8EoEiABPxeKzFWma5pra5UEbg3Wst1hrwr6feuvcy7Sov7cpuYx94ypuy1PQ9NDNoQagFs37wGALzLb5Ei3FvyJWPPPKZ - - 2 Inputs - - 56,522,834 sats - - 1,990,245,069 sats - - 4 Outputs - - 1 Output to another wallet (bcrt1q7cw0wzy8g6mq5qvkpvhnk5gsps5ncy3srp0n2j) of 123,456 sats - - 3 Outputs change - - 3 outputs to emulate a fake mix to increase privacy - - Change addresses are index 1/7, 1/8, 1/9 - - 1/7 address bcrt1q53j0xwuskuf5gnvynadh0hlazyy8srydlucrhg with amount 123,456 sats - - 1/8 address bcrt1q5gtw3zfp4cx67yk5q42q6j6rfza8aqcwpyyslv with amount 1,990,121,477 sats - - 1/9 address bcrt1q9rrg7399m43cn0yg4tz0v0ate89jgf2d6kpz7v with amount 56,399,242 sats - - Fee 272 sats - """ - view.decoder.add_data("cHNidP8BANgCAAAAAsTXZs3fz/dmGb6M80+jjvJZdYya+cw5bT/dGuhZFdSlAAAAAAD9////qo6xg/UZAvUkcbse1F+C9zbP/FeZNjThx7SCIn6eMCgBAAAAAP3///8EQOIBAAAAAAAWABSkZPM7kLcTRE2En1t33/0RCHgMjQXYnnYAAAAAFgAUKMaPRKXdY4m8iKrE9j+rycskJU1A4gEAAAAAABYAFPYc9wiHRrYKAZYLLztREAwpPBIwipVcAwAAAAAWABSiFuiJIa4NrxLUBVQNS0NIun6DDtoRAABPAQQ1h88DBcQGZIAAAAA+0J+jlNL3dpWwlnBi8Dx+Ipg4e6uvB3HdjzFPX7r9CAOOlAIxgII+/xCcj+XoEenKH7wj5s5wlu7Q7CCZWFLGLhA5Su0UVAAAgAEAAIAAAACAAAEA7QIAAAAEE6njX/fnvn7hbkKIRcxzNYFOSfbCdNeWnd7Fe/1UcQ0BAAAAAP3///8TqeNf9+e+fuFuQohFzHM1gU5J9sJ015ad3sV7/VRxDQMAAAAA/f///xOp41/3575+4W5CiEXMczWBTkn2wnTXlp3exXv9VHENBAAAAAD9////E6njX/fnvn7hbkKIRcxzNYFOSfbCdNeWnd7Fe/1UcQ0GAAAAAP3///8CUnheAwAAAAAWABRCfygPJ+Fjsx4BknYvvm3A3qKn2xJ/XQcAAAAAF6kU1I4TAst5nAj15ey7vwe5cM3OFq+HlhEAAAEBH1J4XgMAAAAAFgAUQn8oDyfhY7MeAZJ2L75twN6ip9sBAwQBAAAAIgYCo7sfm78RQY3B5n0ac/QF8VtMAzFnci+h5D1MtpgRY7oYOUrtFFQAAIABAACAAAAAgAEAAAAGAAAAAAEAcQIAAAABxY7wh0nsfJQfzWrD/9rN9BYsM+iOmPaO6I0ANFgO/PcAAAAAAP3///8CptiUAAAAAAAWABRIm4HhQY/TzOjeWSPRrbuJo9MlW826oHYAAAAAFgAU0z+0L2QSLGtyQTn8FhbCpcI7jbliAQAAAQEfzbqgdgAAAAAWABTTP7QvZBIsa3JBOfwWFsKlwjuNuQEDBAEAAAAiBgITHmebEANk81CraV4xZIpqkNjjw0tIvezl1Ism1NRH3Rg5Su0UVAAAgAEAAIAAAACAAQAAAAAAAAAAIgICuTT7WnuiUTpObjWnZFHzIeEvW9PTB+1LLVFNQJVFeIIYOUrtFFQAAIABAACAAAAAgAEAAAAHAAAAACICAk8f3hpc5C35chgSg+Pe2zZ9IhHREd4aKW2+yAMRIFeqGDlK7RRUAACAAQAAgAAAAIABAAAACQAAAAAAIgIDjt1CjvrnMMnjbmTNKUAYoKEDRbmKjNjbq+6Ppqj3bqQYOUrtFFQAAIABAACAAAAAgAEAAAAIAAAAAA==") + def test_scan_psbt_first_then_correct_seedqr_flow(self): + """ + Selecting "Scan" from the MainMenuView and scanning a PSBT should enter the PSBTSelectSeedView flow + when Scan a Seed is selected from PSBTSelectSeedView it should enter the ScanView flow + when a SeedQR is scanned it should enter the PSBTOverviewView flow + since the PSBT has change no warning is displayed and it should enter the PSBTMathView flow + since the PSBT is not a self transfer it should enter the PSBTAddressDetailsView flow + """ + def load_psbt_into_decoder(view: scan_views.ScanView): + """ + PSBT Tx and Wallet Details + - Single Sig Wallet P2WPKH (Native Segwit) with no passphrase + - Regtest c751dc07 m/84'/1'/0' tpubDDZBrnxMxbVzqt8EoEiABPxeKzFWma5pra5UEbg3Wst1hrwr6feuvcy7Sov7cpuYx94ypuy1PQ9NDNoQagFs37wGALzLb5Ei3FvyJWPPPKZ + - 2 Inputs + - 56,522,834 sats + - 1,990,245,069 sats + - 4 Outputs + - 1 Output to another wallet (bcrt1q7cw0wzy8g6mq5qvkpvhnk5gsps5ncy3srp0n2j) of 123,456 sats + - 3 Outputs change + - 3 outputs to emulate a fake mix to increase privacy + - Change addresses are index 1/7, 1/8, 1/9 + - 1/7 address bcrt1q53j0xwuskuf5gnvynadh0hlazyy8srydlucrhg with amount 123,456 sats + - 1/8 address bcrt1q5gtw3zfp4cx67yk5q42q6j6rfza8aqcwpyyslv with amount 1,990,121,477 sats + - 1/9 address bcrt1q9rrg7399m43cn0yg4tz0v0ate89jgf2d6kpz7v with amount 56,399,242 sats + - Fee 272 sats + """ + view.decoder.add_data("cHNidP8BANgCAAAAAsTXZs3fz/dmGb6M80+jjvJZdYya+cw5bT/dGuhZFdSlAAAAAAD9////qo6xg/UZAvUkcbse1F+C9zbP/FeZNjThx7SCIn6eMCgBAAAAAP3///8EQOIBAAAAAAAWABSkZPM7kLcTRE2En1t33/0RCHgMjQXYnnYAAAAAFgAUKMaPRKXdY4m8iKrE9j+rycskJU1A4gEAAAAAABYAFPYc9wiHRrYKAZYLLztREAwpPBIwipVcAwAAAAAWABSiFuiJIa4NrxLUBVQNS0NIun6DDtoRAABPAQQ1h88DBcQGZIAAAAA+0J+jlNL3dpWwlnBi8Dx+Ipg4e6uvB3HdjzFPX7r9CAOOlAIxgII+/xCcj+XoEenKH7wj5s5wlu7Q7CCZWFLGLhA5Su0UVAAAgAEAAIAAAACAAAEA7QIAAAAEE6njX/fnvn7hbkKIRcxzNYFOSfbCdNeWnd7Fe/1UcQ0BAAAAAP3///8TqeNf9+e+fuFuQohFzHM1gU5J9sJ015ad3sV7/VRxDQMAAAAA/f///xOp41/3575+4W5CiEXMczWBTkn2wnTXlp3exXv9VHENBAAAAAD9////E6njX/fnvn7hbkKIRcxzNYFOSfbCdNeWnd7Fe/1UcQ0GAAAAAP3///8CUnheAwAAAAAWABRCfygPJ+Fjsx4BknYvvm3A3qKn2xJ/XQcAAAAAF6kU1I4TAst5nAj15ey7vwe5cM3OFq+HlhEAAAEBH1J4XgMAAAAAFgAUQn8oDyfhY7MeAZJ2L75twN6ip9sBAwQBAAAAIgYCo7sfm78RQY3B5n0ac/QF8VtMAzFnci+h5D1MtpgRY7oYOUrtFFQAAIABAACAAAAAgAEAAAAGAAAAAAEAcQIAAAABxY7wh0nsfJQfzWrD/9rN9BYsM+iOmPaO6I0ANFgO/PcAAAAAAP3///8CptiUAAAAAAAWABRIm4HhQY/TzOjeWSPRrbuJo9MlW826oHYAAAAAFgAU0z+0L2QSLGtyQTn8FhbCpcI7jbliAQAAAQEfzbqgdgAAAAAWABTTP7QvZBIsa3JBOfwWFsKlwjuNuQEDBAEAAAAiBgITHmebEANk81CraV4xZIpqkNjjw0tIvezl1Ism1NRH3Rg5Su0UVAAAgAEAAIAAAACAAQAAAAAAAAAAIgICuTT7WnuiUTpObjWnZFHzIeEvW9PTB+1LLVFNQJVFeIIYOUrtFFQAAIABAACAAAAAgAEAAAAHAAAAACICAk8f3hpc5C35chgSg+Pe2zZ9IhHREd4aKW2+yAMRIFeqGDlK7RRUAACAAQAAgAAAAIABAAAACQAAAAAAIgIDjt1CjvrnMMnjbmTNKUAYoKEDRbmKjNjbq+6Ppqj3bqQYOUrtFFQAAIABAACAAAAAgAEAAAAIAAAAAA==") - def load_seed_into_decoder(view: scan_views.ScanView): - view.decoder.add_data("080115060387063104071857067618681125136207731354") - - self.run_sequence([ - FlowStep(MainMenuView, button_data_selection=MainMenuView.SCAN), - FlowStep(scan_views.ScanView, before_run=load_psbt_into_decoder), # simulate read PSBT; ret val is ignored - FlowStep(psbt_views.PSBTSelectSeedView, button_data_selection=psbt_views.PSBTSelectSeedView.SCAN_SEED), - FlowStep(scan_views.ScanSeedQRView, before_run=load_seed_into_decoder), - FlowStep(seed_views.SeedFinalizeView, button_data_selection=seed_views.SeedFinalizeView.FINALIZE), - FlowStep(seed_views.SeedOptionsView, is_redirect=True), - FlowStep(psbt_views.PSBTOverviewView), - FlowStep(psbt_views.PSBTMathView), - FlowStep(psbt_views.PSBTAddressDetailsView, button_data_selection=0), - FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), - FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), - FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), - FlowStep(psbt_views.PSBTFinalizeView, button_data_selection=psbt_views.PSBTFinalizeView.APPROVE_PSBT), - FlowStep(psbt_views.PSBTSignedQRDisplayView), - FlowStep(MainMenuView) - ]) + def load_seed_into_decoder(view: scan_views.ScanView): + view.decoder.add_data("080115060387063104071857067618681125136207731354") + + self.run_sequence([ + FlowStep(MainMenuView, button_data_selection=MainMenuView.SCAN), + FlowStep(scan_views.ScanView, before_run=load_psbt_into_decoder), # simulate read PSBT; ret val is ignored + FlowStep(psbt_views.PSBTSelectSeedView, button_data_selection=psbt_views.PSBTSelectSeedView.SCAN_SEED), + FlowStep(scan_views.ScanSeedQRView, before_run=load_seed_into_decoder), + FlowStep(seed_views.SeedFinalizeView, button_data_selection=seed_views.SeedFinalizeView.FINALIZE), + FlowStep(seed_views.SeedOptionsView, is_redirect=True), + FlowStep(psbt_views.PSBTOverviewView), + FlowStep(psbt_views.PSBTMathView), + FlowStep(psbt_views.PSBTAddressDetailsView, button_data_selection=0), + FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), + FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), + FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), + FlowStep(psbt_views.PSBTFinalizeView, button_data_selection=psbt_views.PSBTFinalizeView.APPROVE_PSBT), + FlowStep(psbt_views.PSBTSignedQRDisplayView), + FlowStep(MainMenuView) + ]) - - def test_scan_multisig_psbt_seed_already_signed_flow(self): - - def load_psbt_into_decoder(view: scan_views.ScanView): - view.decoder.add_data("cHNidP8BAIkCAAAAAc9dCSh2RcRPfHaT5bNVBpbg0jAekRLqOK+bpN/QA0jeAAAAAAD9////AtAHAAAAAAAAIlEg24shYsV3IRCzlgmMKjAsR4Ad9tX896z7zDAi5q0TU9H3CgAAAAAAACIAIByGQg/VP2aRID62ty40E64HYZeRRsKRGLt8J/76R6stQ04FAE8BBDWHzwSLLGdzgAAAAq3q6nR20JnHR+vKrBQdWxN9C7xU8zNX942mVF7AQpl2ArrdLwVlkGxaatQJ4wwkvypNBKbwOq9hXGLNlKi7rZWAFDUxzXUwAACAAQAAgAAAAIACAACATwEENYfPBHOCZmWAAAACmH6KTXIny0vueRgQFBq4M6oMuG8f1QM0I/RzKQ03bCgCHrF0fyUtV0+FD2N34u/woqb8MAt/o+7Ed58RddhY8zYUCUjSaDAAAIABAACAAAAAgAIAAIAAAQEriBMAAAAAAAAiACBY4WsjDgJXLj3VW222jU1tkIIhT26ce/2efH73BWGGBiICAqyfkrdUO662QBrdvJcSOZMFxniD7M1awm9U0Kb5XCm5RzBEAiAPkQTY84YjFFkpD6MI2cc5rJySqws5fsTQA/8XEZFpbAIgTNVykbEH4Z7bqyzhhy6lty0K8rtCUDCaHNv+47NNIWgBAQMEAQAAAAEFR1IhApL4XO+VE1pPYn5wnRFyJQKVSc9TX2dO6KIBH6jwvgPaIQKsn5K3VDuutkAa3byXEjmTBcZ4g+zNWsJvVNCm+VwpuVKuIgYCkvhc75UTWk9ifnCdEXIlApVJz1NfZ07oogEfqPC+A9ocNTHNdTAAAIABAACAAAAAgAIAAIAAAAAAAAAAACIGAqyfkrdUO662QBrdvJcSOZMFxniD7M1awm9U0Kb5XCm5HAlI0mgwAACAAQAAgAAAAIACAACAAAAAAAAAAAAAAAEBR1IhApYXaczuYbBM/A+EH639Ir2yIB4PxL46dK/I1V1O9aHgIQLa02HCI/+EP+9gGpxHskjYWFN5hZzXY7RRvwV4UF42ylKuIgIClhdpzO5hsEz8D4Qfrf0ivbIgHg/Evjp0r8jVXU71oeAcNTHNdTAAAIABAACAAAAAgAIAAIABAAAAAAAAACICAtrTYcIj/4Q/72AanEeySNhYU3mFnNdjtFG/BXhQXjbKHAlI0mgwAACAAQAAgAAAAIACAACAAQAAAAAAAAAA") - - def load_seed_into_decoder(view: scan_views.ScanView): - view.decoder.add_data("073318950739065415961602009907670428187212261116") - - self.run_sequence([ - FlowStep(MainMenuView, button_data_selection=MainMenuView.SCAN), - FlowStep(scan_views.ScanView, before_run=load_psbt_into_decoder), # simulate read PSBT; ret val is ignored - FlowStep(psbt_views.PSBTSelectSeedView, button_data_selection=psbt_views.PSBTSelectSeedView.SCAN_SEED), - FlowStep(scan_views.ScanSeedQRView, before_run=load_seed_into_decoder), - FlowStep(seed_views.SeedFinalizeView, button_data_selection=seed_views.SeedFinalizeView.FINALIZE), - FlowStep(seed_views.SeedOptionsView, is_redirect=True), - FlowStep(psbt_views.PSBTOverviewView), - FlowStep(psbt_views.PSBTMathView), - FlowStep(psbt_views.PSBTAddressDetailsView, button_data_selection=0), - FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.SKIP_VERIFICATION), - FlowStep(psbt_views.PSBTFinalizeView, button_data_selection=psbt_views.PSBTFinalizeView.APPROVE_PSBT), - FlowStep(psbt_views.PSBTSigningErrorView, button_data_selection=psbt_views.PSBTSigningErrorView.SELECT_DIFF_SEED), - FlowStep(psbt_views.PSBTSelectSeedView, button_data_selection=psbt_views.PSBTSelectSeedView.SCAN_SEED), - FlowStep(scan_views.ScanSeedQRView, before_run=load_seed_into_decoder), - FlowStep(seed_views.SeedFinalizeView, button_data_selection=seed_views.SeedFinalizeView.PASSPHRASE), - FlowStep(seed_views.SeedAddPassphraseView, screen_return_value="abc"), - FlowStep(seed_views.SeedReviewPassphraseView, button_data_selection=seed_views.SeedReviewPassphraseView.DONE), - FlowStep(seed_views.SeedOptionsView, is_redirect=True), - FlowStep(psbt_views.PSBTOverviewView), - FlowStep(psbt_views.PSBTMathView), - FlowStep(psbt_views.PSBTAddressDetailsView, button_data_selection=0), - FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.SKIP_VERIFICATION), - FlowStep(psbt_views.PSBTFinalizeView, button_data_selection=psbt_views.PSBTFinalizeView.APPROVE_PSBT), - FlowStep(psbt_views.PSBTSignedQRDisplayView), - FlowStep(MainMenuView), - ]) - - def load_seed_into_decoder(view: scan_views.ScanView): - view.decoder.add_data("080115060387063104071857067618681125136207731354") + + def test_scan_multisig_psbt_seed_already_signed_flow(self): + + def load_psbt_into_decoder(view: scan_views.ScanView): + view.decoder.add_data("cHNidP8BAIkCAAAAAc9dCSh2RcRPfHaT5bNVBpbg0jAekRLqOK+bpN/QA0jeAAAAAAD9////AtAHAAAAAAAAIlEg24shYsV3IRCzlgmMKjAsR4Ad9tX896z7zDAi5q0TU9H3CgAAAAAAACIAIByGQg/VP2aRID62ty40E64HYZeRRsKRGLt8J/76R6stQ04FAE8BBDWHzwSLLGdzgAAAAq3q6nR20JnHR+vKrBQdWxN9C7xU8zNX942mVF7AQpl2ArrdLwVlkGxaatQJ4wwkvypNBKbwOq9hXGLNlKi7rZWAFDUxzXUwAACAAQAAgAAAAIACAACATwEENYfPBHOCZmWAAAACmH6KTXIny0vueRgQFBq4M6oMuG8f1QM0I/RzKQ03bCgCHrF0fyUtV0+FD2N34u/woqb8MAt/o+7Ed58RddhY8zYUCUjSaDAAAIABAACAAAAAgAIAAIAAAQEriBMAAAAAAAAiACBY4WsjDgJXLj3VW222jU1tkIIhT26ce/2efH73BWGGBiICAqyfkrdUO662QBrdvJcSOZMFxniD7M1awm9U0Kb5XCm5RzBEAiAPkQTY84YjFFkpD6MI2cc5rJySqws5fsTQA/8XEZFpbAIgTNVykbEH4Z7bqyzhhy6lty0K8rtCUDCaHNv+47NNIWgBAQMEAQAAAAEFR1IhApL4XO+VE1pPYn5wnRFyJQKVSc9TX2dO6KIBH6jwvgPaIQKsn5K3VDuutkAa3byXEjmTBcZ4g+zNWsJvVNCm+VwpuVKuIgYCkvhc75UTWk9ifnCdEXIlApVJz1NfZ07oogEfqPC+A9ocNTHNdTAAAIABAACAAAAAgAIAAIAAAAAAAAAAACIGAqyfkrdUO662QBrdvJcSOZMFxniD7M1awm9U0Kb5XCm5HAlI0mgwAACAAQAAgAAAAIACAACAAAAAAAAAAAAAAAEBR1IhApYXaczuYbBM/A+EH639Ir2yIB4PxL46dK/I1V1O9aHgIQLa02HCI/+EP+9gGpxHskjYWFN5hZzXY7RRvwV4UF42ylKuIgIClhdpzO5hsEz8D4Qfrf0ivbIgHg/Evjp0r8jVXU71oeAcNTHNdTAAAIABAACAAAAAgAIAAIABAAAAAAAAACICAtrTYcIj/4Q/72AanEeySNhYU3mFnNdjtFG/BXhQXjbKHAlI0mgwAACAAQAAgAAAAIACAACAAQAAAAAAAAAA") + + def load_seed_into_decoder(view: scan_views.ScanView): + view.decoder.add_data("073318950739065415961602009907670428187212261116") + + self.run_sequence([ + FlowStep(MainMenuView, button_data_selection=MainMenuView.SCAN), + FlowStep(scan_views.ScanView, before_run=load_psbt_into_decoder), # simulate read PSBT; ret val is ignored + FlowStep(psbt_views.PSBTSelectSeedView, button_data_selection=psbt_views.PSBTSelectSeedView.SCAN_SEED), + FlowStep(scan_views.ScanSeedQRView, before_run=load_seed_into_decoder), + FlowStep(seed_views.SeedFinalizeView, button_data_selection=seed_views.SeedFinalizeView.FINALIZE), + FlowStep(seed_views.SeedOptionsView, is_redirect=True), + FlowStep(psbt_views.PSBTOverviewView), + FlowStep(psbt_views.PSBTMathView), + FlowStep(psbt_views.PSBTAddressDetailsView, button_data_selection=0), + FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.SKIP_VERIFICATION), + FlowStep(psbt_views.PSBTFinalizeView, button_data_selection=psbt_views.PSBTFinalizeView.APPROVE_PSBT), + FlowStep(psbt_views.PSBTSigningErrorView, button_data_selection=psbt_views.PSBTSigningErrorView.SELECT_DIFF_SEED), + FlowStep(psbt_views.PSBTSelectSeedView, button_data_selection=psbt_views.PSBTSelectSeedView.SCAN_SEED), + FlowStep(scan_views.ScanSeedQRView, before_run=load_seed_into_decoder), + FlowStep(seed_views.SeedFinalizeView, button_data_selection=seed_views.SeedFinalizeView.PASSPHRASE), + FlowStep(seed_views.SeedAddPassphraseView, screen_return_value="abc"), + FlowStep(seed_views.SeedReviewPassphraseView, button_data_selection=seed_views.SeedReviewPassphraseView.DONE), + FlowStep(seed_views.SeedOptionsView, is_redirect=True), + FlowStep(psbt_views.PSBTOverviewView), + FlowStep(psbt_views.PSBTMathView), + FlowStep(psbt_views.PSBTAddressDetailsView, button_data_selection=0), + FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.SKIP_VERIFICATION), + FlowStep(psbt_views.PSBTFinalizeView, button_data_selection=psbt_views.PSBTFinalizeView.APPROVE_PSBT), + FlowStep(psbt_views.PSBTSignedQRDisplayView), + FlowStep(MainMenuView), + ]) + + def load_seed_into_decoder(view: scan_views.ScanView): + view.decoder.add_data("080115060387063104071857067618681125136207731354") - self.run_sequence([ - FlowStep(MainMenuView, button_data_selection=MainMenuView.SCAN), - FlowStep(scan_views.ScanView, before_run=load_psbt_into_decoder), # simulate read PSBT; ret val is ignored - FlowStep(psbt_views.PSBTSelectSeedView, button_data_selection=psbt_views.PSBTSelectSeedView.SCAN_SEED), - FlowStep(scan_views.ScanSeedQRView, before_run=load_seed_into_decoder), - FlowStep(seed_views.SeedFinalizeView, button_data_selection=seed_views.SeedFinalizeView.FINALIZE), - FlowStep(seed_views.SeedOptionsView, is_redirect=True), - FlowStep(psbt_views.PSBTOverviewView), - FlowStep(psbt_views.PSBTMathView), - FlowStep(psbt_views.PSBTAddressDetailsView, button_data_selection=0), - FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), - FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), - FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), - FlowStep(psbt_views.PSBTFinalizeView, button_data_selection=psbt_views.PSBTFinalizeView.APPROVE_PSBT), - FlowStep(psbt_views.PSBTSignedQRDisplayView), - FlowStep(MainMenuView) - ]) + self.run_sequence([ + FlowStep(MainMenuView, button_data_selection=MainMenuView.SCAN), + FlowStep(scan_views.ScanView, before_run=load_psbt_into_decoder), # simulate read PSBT; ret val is ignored + FlowStep(psbt_views.PSBTSelectSeedView, button_data_selection=psbt_views.PSBTSelectSeedView.SCAN_SEED), + FlowStep(scan_views.ScanSeedQRView, before_run=load_seed_into_decoder), + FlowStep(seed_views.SeedFinalizeView, button_data_selection=seed_views.SeedFinalizeView.FINALIZE), + FlowStep(seed_views.SeedOptionsView, is_redirect=True), + FlowStep(psbt_views.PSBTOverviewView), + FlowStep(psbt_views.PSBTMathView), + FlowStep(psbt_views.PSBTAddressDetailsView, button_data_selection=0), + FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), + FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), + FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), + FlowStep(psbt_views.PSBTFinalizeView, button_data_selection=psbt_views.PSBTFinalizeView.APPROVE_PSBT), + FlowStep(psbt_views.PSBTSignedQRDisplayView), + FlowStep(MainMenuView) + ]) - def test_parse_and_display_op_return_content(self): - """ - PSBTs that include an OP_RETURN should be able to be parsed like any other - PSBT and route to the dedicated OP_RETURN View to display the content - """ - def load_psbt_into_decoder(view: scan_views.ScanView): - """ - PSBT Tx and Wallet Details - - Single Sig Wallet P2WPKH (Native Segwit) with no passphrase - - Regtest 0fb882ff m/84'/1'/0' tpubDCfk37PqcQx6nFtFVuYHvRLJHxvYj33NjHkKRyRmWyCjyJ64sYBXyVjsTHaLBp5GLhM91VBgJ8nKDWDu52J2xVRy64c7ybEjjyWQJuQGLcg - - 1 Input - - 99,992,460 sats - - 2 Outputs - - 1 Output back to self (bcrt1qvwkhakqhz7m7kmz6332avatsmdy32m644g86vv) of 99,992,296 sats - - 1 OP_RETURN: "Chancellor on the brink of third bailout" - - Fee 164 sats - """ - view.decoder.add_data("cHNidP8BAIYCAAAAATpQ10o+gKdZ8ThpKsbfHiHYn3NhvUrQ5DvW0ZWX8jKLAAAAAAD9////AujC9QUAAAAAFgAUY61+2BcXt+tsWoxV1nVw20kVb1UAAAAAAAAAACtqTChDaGFuY2VsbG9yIG9uIHRoZSBicmluayBvZiB0aGlyZCBiYWlsb3V0aQAAAE8BBDWHzwNXmUmVgAAAANRFa7R5gYD84Wbha3d1QnjgfYPOBw87on6cXS32WoyqAsPFtPxB7PRTdbujUnBPUVDh9YUBtwrl4nc0OcRNGvIyEA+4gv9UAACAAQAAgAAAAIAAAQB0AgAAAAGNFK/1X0fP5q+nu5XX7Tk2VRa0EL+jkGI9CHiJvsjZCgAAAAAA/f///wKMw/UFAAAAABYAFIpZMNnUU6cQt8Q0YpZ0pnvsSA5fAAAAAAAAAAAZakwWYml0Y29pbiBpcyBmcmVlIHNwZWVjaGgAAAABAR+Mw/UFAAAAABYAFIpZMNnUU6cQt8Q0YpZ0pnvsSA5fAQMEAQAAACIGAvxDI0eNI1oQ2AU69R7A0jf+hUdilWCgrWHgdzkqlaXMGA+4gv9UAACAAQAAgAAAAIAAAAAAAQAAAAAiAgK9qKtzGWyiRrpmupdA99NVLriz3GQy6cENbyD19sfl/hgPuIL/VAAAgAEAAIAAAACAAAAAAAIAAAAAAA==") + def test_parse_and_display_op_return_content(self): + """ + PSBTs that include an OP_RETURN should be able to be parsed like any other + PSBT and route to the dedicated OP_RETURN View to display the content + """ + def load_psbt_into_decoder(view: scan_views.ScanView): + """ + PSBT Tx and Wallet Details + - Single Sig Wallet P2WPKH (Native Segwit) with no passphrase + - Regtest 0fb882ff m/84'/1'/0' tpubDCfk37PqcQx6nFtFVuYHvRLJHxvYj33NjHkKRyRmWyCjyJ64sYBXyVjsTHaLBp5GLhM91VBgJ8nKDWDu52J2xVRy64c7ybEjjyWQJuQGLcg + - 1 Input + - 99,992,460 sats + - 2 Outputs + - 1 Output back to self (bcrt1qvwkhakqhz7m7kmz6332avatsmdy32m644g86vv) of 99,992,296 sats + - 1 OP_RETURN: "Chancellor on the brink of third bailout" + - Fee 164 sats + """ + view.decoder.add_data("cHNidP8BAIYCAAAAATpQ10o+gKdZ8ThpKsbfHiHYn3NhvUrQ5DvW0ZWX8jKLAAAAAAD9////AujC9QUAAAAAFgAUY61+2BcXt+tsWoxV1nVw20kVb1UAAAAAAAAAACtqTChDaGFuY2VsbG9yIG9uIHRoZSBicmluayBvZiB0aGlyZCBiYWlsb3V0aQAAAE8BBDWHzwNXmUmVgAAAANRFa7R5gYD84Wbha3d1QnjgfYPOBw87on6cXS32WoyqAsPFtPxB7PRTdbujUnBPUVDh9YUBtwrl4nc0OcRNGvIyEA+4gv9UAACAAQAAgAAAAIAAAQB0AgAAAAGNFK/1X0fP5q+nu5XX7Tk2VRa0EL+jkGI9CHiJvsjZCgAAAAAA/f///wKMw/UFAAAAABYAFIpZMNnUU6cQt8Q0YpZ0pnvsSA5fAAAAAAAAAAAZakwWYml0Y29pbiBpcyBmcmVlIHNwZWVjaGgAAAABAR+Mw/UFAAAAABYAFIpZMNnUU6cQt8Q0YpZ0pnvsSA5fAQMEAQAAACIGAvxDI0eNI1oQ2AU69R7A0jf+hUdilWCgrWHgdzkqlaXMGA+4gv9UAACAAQAAgAAAAIAAAAAAAQAAAAAiAgK9qKtzGWyiRrpmupdA99NVLriz3GQy6cENbyD19sfl/hgPuIL/VAAAgAEAAIAAAACAAAAAAAIAAAAAAA==") - def load_seed_into_decoder(view: scan_views.ScanView): - view.decoder.add_data("114006021552133507590698063102151531110102551496") + def load_seed_into_decoder(view: scan_views.ScanView): + view.decoder.add_data("114006021552133507590698063102151531110102551496") - self.run_sequence([ - FlowStep(MainMenuView, button_data_selection=MainMenuView.SCAN), - FlowStep(scan_views.ScanView, before_run=load_psbt_into_decoder), # simulate read PSBT; ret val is ignored - FlowStep(psbt_views.PSBTSelectSeedView, button_data_selection=psbt_views.PSBTSelectSeedView.SCAN_SEED), - FlowStep(scan_views.ScanSeedQRView, before_run=load_seed_into_decoder), - FlowStep(seed_views.SeedFinalizeView, button_data_selection=seed_views.SeedFinalizeView.FINALIZE), - FlowStep(seed_views.SeedOptionsView, is_redirect=True), - FlowStep(psbt_views.PSBTOverviewView), - FlowStep(psbt_views.PSBTMathView), - FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), + self.run_sequence([ + FlowStep(MainMenuView, button_data_selection=MainMenuView.SCAN), + FlowStep(scan_views.ScanView, before_run=load_psbt_into_decoder), # simulate read PSBT; ret val is ignored + FlowStep(psbt_views.PSBTSelectSeedView, button_data_selection=psbt_views.PSBTSelectSeedView.SCAN_SEED), + FlowStep(scan_views.ScanSeedQRView, before_run=load_seed_into_decoder), + FlowStep(seed_views.SeedFinalizeView, button_data_selection=seed_views.SeedFinalizeView.FINALIZE), + FlowStep(seed_views.SeedOptionsView, is_redirect=True), + FlowStep(psbt_views.PSBTOverviewView), + FlowStep(psbt_views.PSBTMathView), + FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), - # Should route to display OP_RETURN content - FlowStep(psbt_views.PSBTOpReturnView, button_data_selection=0), + # Should route to display OP_RETURN content + FlowStep(psbt_views.PSBTOpReturnView, button_data_selection=0), - # Should be able to sign the psbt - FlowStep(psbt_views.PSBTFinalizeView, button_data_selection=psbt_views.PSBTFinalizeView.APPROVE_PSBT), - FlowStep(psbt_views.PSBTSignedQRDisplayView), - FlowStep(MainMenuView) - ]) + # Should be able to sign the psbt + FlowStep(psbt_views.PSBTFinalizeView, button_data_selection=psbt_views.PSBTFinalizeView.APPROVE_PSBT), + FlowStep(psbt_views.PSBTSignedQRDisplayView), + FlowStep(MainMenuView) + ]) From 3600d63b03187da3775f705f48ebadee76cfdf1f Mon Sep 17 00:00:00 2001 From: kdmukai Date: Tue, 9 Jul 2024 08:07:00 -0500 Subject: [PATCH 12/15] merge cleanup --- tests/test_flows_psbt.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/tests/test_flows_psbt.py b/tests/test_flows_psbt.py index b81cfe9e..0b020dfc 100644 --- a/tests/test_flows_psbt.py +++ b/tests/test_flows_psbt.py @@ -92,27 +92,6 @@ def load_seed_into_decoder(view: scan_views.ScanView): FlowStep(psbt_views.PSBTSignedQRDisplayView), FlowStep(MainMenuView), ]) - - def load_seed_into_decoder(view: scan_views.ScanView): - view.decoder.add_data("080115060387063104071857067618681125136207731354") - - self.run_sequence([ - FlowStep(MainMenuView, button_data_selection=MainMenuView.SCAN), - FlowStep(scan_views.ScanView, before_run=load_psbt_into_decoder), # simulate read PSBT; ret val is ignored - FlowStep(psbt_views.PSBTSelectSeedView, button_data_selection=psbt_views.PSBTSelectSeedView.SCAN_SEED), - FlowStep(scan_views.ScanSeedQRView, before_run=load_seed_into_decoder), - FlowStep(seed_views.SeedFinalizeView, button_data_selection=seed_views.SeedFinalizeView.FINALIZE), - FlowStep(seed_views.SeedOptionsView, is_redirect=True), - FlowStep(psbt_views.PSBTOverviewView), - FlowStep(psbt_views.PSBTMathView), - FlowStep(psbt_views.PSBTAddressDetailsView, button_data_selection=0), - FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), - FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), - FlowStep(psbt_views.PSBTChangeDetailsView, button_data_selection=psbt_views.PSBTChangeDetailsView.NEXT), - FlowStep(psbt_views.PSBTFinalizeView, button_data_selection=psbt_views.PSBTFinalizeView.APPROVE_PSBT), - FlowStep(psbt_views.PSBTSignedQRDisplayView), - FlowStep(MainMenuView) - ]) def test_parse_and_display_op_return_content(self): From b45d807a0e41fec40c0649579fd9cc1707ebefe0 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Tue, 9 Jul 2024 08:08:13 -0500 Subject: [PATCH 13/15] Update test_flows_psbt.py --- tests/test_flows_psbt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_flows_psbt.py b/tests/test_flows_psbt.py index 0b020dfc..b2653b01 100644 --- a/tests/test_flows_psbt.py +++ b/tests/test_flows_psbt.py @@ -4,7 +4,7 @@ from seedsigner.views import scan_views, seed_views, psbt_views - +# TODO: Cleanup: convert TAB spacing to SPACE class TestPSBTFlows(FlowTest): def test_scan_psbt_first_then_correct_seedqr_flow(self): From 75636bbddedaabdb1233f4570bc3ca331c381240 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Tue, 9 Jul 2024 08:13:49 -0500 Subject: [PATCH 14/15] Restore fix for op_code support --- src/seedsigner/models/psbt_parser.py | 31 ++++++++++++++-------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/seedsigner/models/psbt_parser.py b/src/seedsigner/models/psbt_parser.py index b0af2ff8..2846e6b5 100644 --- a/src/seedsigner/models/psbt_parser.py +++ b/src/seedsigner/models/psbt_parser.py @@ -262,21 +262,22 @@ def _get_policy(scope, scriptpubkey, xpubs): # expected multisig script = None - if "p2wsh" in script_type and scope.witness_script is not None: - script = scope.witness_script - - elif "p2sh" in script_type and scope.redeem_script is not None: - script = scope.redeem_script - - if script is not None: - m, n, pubkeys = PSBTParser._parse_multisig(script) - - # check pubkeys are derived from cosigners - try: - cosigners = PSBTParser._get_cosigners(pubkeys, scope.bip32_derivations, xpubs) - policy.update({"m": m, "n": n, "cosigners": cosigners}) - except: - policy.update({"m": m, "n": n}) + if script_type: + if "p2wsh" in script_type and scope.witness_script is not None: + script = scope.witness_script + + elif "p2sh" in script_type and scope.redeem_script is not None: + script = scope.redeem_script + + if script is not None: + m, n, pubkeys = PSBTParser._parse_multisig(script) + + # check pubkeys are derived from cosigners + try: + cosigners = PSBTParser._get_cosigners(pubkeys, scope.bip32_derivations, xpubs) + policy.update({"m": m, "n": n, "cosigners": cosigners}) + except: + policy.update({"m": m, "n": n}) return policy From df7bc42377caa8c81943ab7e0beda9698e2f3645 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Tue, 9 Jul 2024 08:35:33 -0500 Subject: [PATCH 15/15] Minor cleanup --- src/seedsigner/gui/screens/psbt_screens.py | 6 +++--- src/seedsigner/gui/screens/seed_screens.py | 2 ++ src/seedsigner/models/psbt_parser.py | 4 ++-- src/seedsigner/views/psbt_views.py | 8 ++++---- tests/test_psbt_parser.py | 2 +- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/seedsigner/gui/screens/psbt_screens.py b/src/seedsigner/gui/screens/psbt_screens.py index 369e93af..502c3921 100644 --- a/src/seedsigner/gui/screens/psbt_screens.py +++ b/src/seedsigner/gui/screens/psbt_screens.py @@ -688,7 +688,7 @@ def __post_init__(self): @dataclass class PSBTOpReturnScreen(ButtonListScreen): - op_return: str = None + op_return_data: bytes = None def __post_init__(self): # Customize defaults @@ -699,7 +699,7 @@ def __post_init__(self): try: # Simple case: display human-readable text self.components.append(TextArea( - text=self.op_return.decode(errors="strict"), + text=self.op_return_data.decode(errors="strict"), # "strict" is a good enough heuristic to decide if it's human readable font_size=GUIConstants.TOP_NAV_TITLE_FONT_SIZE, is_text_centered=True, allow_text_overflow=True, @@ -713,7 +713,7 @@ def __post_init__(self): font = Fonts.get_font(GUIConstants.FIXED_WIDTH_FONT_NAME, size=GUIConstants.BODY_FONT_SIZE) (left, top, right, bottom) = font.getbbox("X", anchor="ls") chars_per_line = int((self.canvas_width - 2*GUIConstants.EDGE_PADDING) / (right - left)) - decoded_str = self.op_return.hex() + decoded_str = self.op_return_data.hex() num_lines = math.ceil(len(decoded_str) / chars_per_line) text = "" for i in range(num_lines): diff --git a/src/seedsigner/gui/screens/seed_screens.py b/src/seedsigner/gui/screens/seed_screens.py index 9fe35c1c..89e42daa 100644 --- a/src/seedsigner/gui/screens/seed_screens.py +++ b/src/seedsigner/gui/screens/seed_screens.py @@ -1511,6 +1511,8 @@ def __post_init__(self): end_y = renderer.canvas_height - GUIConstants.EDGE_PADDING - GUIConstants.BUTTON_HEIGHT - GUIConstants.COMPONENT_PADDING message_height = end_y - start_y + # TODO: Pass the full message in from the View so that this Screen doesn't need to + # interact with the Controller here. self.sign_message_data = Controller.get_instance().sign_message_data if "paged_message" not in self.sign_message_data: paged = reflow_text_into_pages( diff --git a/src/seedsigner/models/psbt_parser.py b/src/seedsigner/models/psbt_parser.py index 2846e6b5..8faba3fe 100644 --- a/src/seedsigner/models/psbt_parser.py +++ b/src/seedsigner/models/psbt_parser.py @@ -31,7 +31,7 @@ def __init__(self, p: PSBT, seed: Seed, network: str = SettingsConstants.MAINNET self.num_inputs = 0 self.destination_addresses = [] self.destination_amounts = [] - self.op_return = None + self.op_return_data: bytes = None self.root = None @@ -177,7 +177,7 @@ def _parse_outputs(self): if self.psbt.tx.vout[i].script_pubkey.data[0] == OPCODES.OP_RETURN: # The data is written as: OP_RETURN + OP_PUSHDATA1 + len(payload) + payload - self.op_return = self.psbt.tx.vout[i].script_pubkey.data[3:] + self.op_return_data = self.psbt.tx.vout[i].script_pubkey.data[3:] elif is_change: addr = self.psbt.tx.vout[i].script_pubkey.address(NETWORKS[SettingsConstants.map_network_to_embit(self.network)]) diff --git a/src/seedsigner/views/psbt_views.py b/src/seedsigner/views/psbt_views.py index e8ac194e..acacfd8c 100644 --- a/src/seedsigner/views/psbt_views.py +++ b/src/seedsigner/views/psbt_views.py @@ -138,7 +138,7 @@ def run(self): num_self_transfer_outputs=num_self_transfer_outputs, num_change_outputs=num_change_outputs, destination_addresses=psbt_parser.destination_addresses, - has_op_return=psbt_parser.op_return is not None, + has_op_return=psbt_parser.op_return_data is not None, ) if selected_menu_num == RET_CODE__BACK_BUTTON: @@ -277,7 +277,7 @@ def run(self): # Move on to display change return Destination(PSBTChangeDetailsView, view_args={"change_address_num": 0}) - elif psbt_parser.op_return: + elif psbt_parser.op_return_data: return Destination(PSBTOpReturnView) else: @@ -421,7 +421,7 @@ def run(self): if self.change_address_num < psbt_parser.num_change_outputs - 1: return Destination(PSBTChangeDetailsView, view_args={"change_address_num": self.change_address_num + 1}) - elif psbt_parser.op_return: + elif psbt_parser.op_return_data: return Destination(PSBTOpReturnView) else: @@ -481,7 +481,7 @@ def run(self): PSBTOpReturnScreen, title=title, button_data=button_data, - op_return=psbt_parser.op_return, + op_return_data=psbt_parser.op_return_data, ) if selected_menu_num == RET_CODE__BACK_BUTTON: diff --git a/tests/test_psbt_parser.py b/tests/test_psbt_parser.py index 801a52d4..99b20c23 100644 --- a/tests/test_psbt_parser.py +++ b/tests/test_psbt_parser.py @@ -140,7 +140,7 @@ def test_parse_op_return_content(): psbt_parser = PSBTParser(p=tx, seed=seed, network=SettingsConstants.REGTEST) # Remember to do the comparison as bytes - assert psbt_parser.op_return == "Chancellor on the brink of third bailout".encode() + assert psbt_parser.op_return_data == "Chancellor on the brink of third bailout".encode() # PSBT is an internal self-spend to the its own receive addr, but the parser categorizes it as "change" assert psbt_parser.change_data == [