From fede7f6f3cf46b9fff327b3e38452a2838be2b16 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Fri, 21 Jan 2022 14:23:05 -0600 Subject: [PATCH] Test suite fixes; adds initial SeedQR format tests --- src/seedsigner/models/decode_qr.py | 16 +++++- tests/test_controller.py | 2 + tests/test_decodepsbtqr.py | 4 +- tests/test_seedqr.py | 90 ++++++++++++++++++++++++++++++ 4 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 tests/test_seedqr.py diff --git a/src/seedsigner/models/decode_qr.py b/src/seedsigner/models/decode_qr.py index c536360a..a91a5dc3 100644 --- a/src/seedsigner/models/decode_qr.py +++ b/src/seedsigner/models/decode_qr.py @@ -71,7 +71,14 @@ class DecodeQR: return rt # Convert to string data - qr_str = data.decode('utf-8') + if type(data) == bytes: + # Should always be bytes, but the test suite has some manual datasets that + # are strings. + # TODO: Convert the test suite rather than handle here? + qr_str = data.decode('utf-8') + else: + # it's already str data + qr_str = data if self.qr_type == QRType.PSBTUR2: @@ -268,7 +275,12 @@ class DecodeQR: logger.debug(len(s)) try: - s = s.decode('utf-8') + # Convert to str data + if type(s) == bytes: + # Should always be bytes, but the test suite has some manual datasets that + # are strings. + # TODO: Convert the test suite rather than handle here? + s = s.decode('utf-8') # PSBT if re.search("^UR:CRYPTO-PSBT/", s, re.IGNORECASE): return QRType.PSBTUR2 diff --git a/tests/test_controller.py b/tests/test_controller.py index 96757eff..67d9d2d6 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -25,6 +25,8 @@ def test_singleton_get_instance_preserves_state(): [display] text_color = ORANGE + qr_background_color = FFFFFF + camera_rotation = 0 [wallet] network = main diff --git a/tests/test_decodepsbtqr.py b/tests/test_decodepsbtqr.py index 80ba14dd..7d673fd2 100644 --- a/tests/test_decodepsbtqr.py +++ b/tests/test_decodepsbtqr.py @@ -432,10 +432,10 @@ def test_specter_wallet_json(): def test_specter_wallet_json2(): - 1_part = '{"label": "Testnet Single Zone", "blockheight": 2090512, "descriptor": "wpkh([990a73ad/84h/1h/0h]tpubDDHQMDnFdan2GyHBsG32VW9qiygbhVizGRTjiS3H79M49FSvpsvLXqLgp1yC7r43dXVHozWavi2Fc4WHUpZmQYmzoQbit28qJhLjScbAQWU/0/*)#ujr0xunp","devices": [{"type": "seedsigner", "label": "Single Seed Zone Testnet"}]}' + part = '{"label": "Testnet Single Zone", "blockheight": 2090512, "descriptor": "wpkh([990a73ad/84h/1h/0h]tpubDDHQMDnFdan2GyHBsG32VW9qiygbhVizGRTjiS3H79M49FSvpsvLXqLgp1yC7r43dXVHozWavi2Fc4WHUpZmQYmzoQbit28qJhLjScbAQWU/0/*)#ujr0xunp","devices": [{"type": "seedsigner", "label": "Single Seed Zone Testnet"}]}' d = DecodeQR(wordlist=bip39.WORDLIST) - d.add_data(1_part) + d.add_data(part) assert d.qrType() == QRType.SPECTERWALLETQR assert d.isComplete() == True diff --git a/tests/test_seedqr.py b/tests/test_seedqr.py new file mode 100644 index 00000000..63d7dc1c --- /dev/null +++ b/tests/test_seedqr.py @@ -0,0 +1,90 @@ +import os +import pyzbar +from embit import bip39 +from seedsigner.helpers.qr import QR +from seedsigner.helpers.ur2.bytewords import decode +from seedsigner.models.decode_qr import DecodeQR, DecodeQRStatus +from seedsigner.models.encode_qr import EncodeQR +from seedsigner.models.qr_type import QRType + + +def run_encode_decode_test(entropy: bytes, mnemonic_length, qr_type): + """ Helper method to re-run multiple variations of the same encode/decode test """ + print(entropy) + seed_phrase = bip39.mnemonic_from_bytes(entropy).split() + print(seed_phrase) + assert len(seed_phrase) == mnemonic_length + + e = EncodeQR(seed_phrase=seed_phrase, qr_type=qr_type, wordlist=bip39.WORDLIST) + data = e.nextPart() + print(data) + + qr = QR() + image = qr.qrimage( + data=data, + width=240, + height=240, + border=3 + ) + + decoder = DecodeQR(wordlist=bip39.WORDLIST) + status = decoder.addImage(image) + assert status == DecodeQRStatus.COMPLETE + + decoded_seed_phrase = decoder.getSeedPhrase() + print(decoded_seed_phrase) + assert seed_phrase == decoded_seed_phrase + + + +def test_standard_seedqr_encode_decode_(): + """ Should encode 24- and 12- word mnemonics to Standard SeedQR format and decode + them back again to their original mnemonic seed phrase. + """ + # 24-word seed + run_encode_decode_test(os.urandom(32), mnemonic_length=24, qr_type=QRType.SEEDQR) + + # 12-word seed + run_encode_decode_test(os.urandom(16), mnemonic_length=12, qr_type=QRType.SEEDQR) + + + +def test_compact_seedqr_encode_decode(): + """ Should encode 24- and 12- word mnemonics to CompactSeedQR format and decode + them back again to their original mnemonic seed phrase. + """ + # 24-word seed + run_encode_decode_test(os.urandom(32), mnemonic_length=24, qr_type=QRType.COMPACTSEEDQR) + + # 12-word seed + run_encode_decode_test(os.urandom(16), mnemonic_length=12, qr_type=QRType.COMPACTSEEDQR) + + + +def test_compact_seedqr_handles_null_bytes(): + """ Should properly encode a CompactSeedQR with null bytes (b'\x00') in the input + entropy and decode it back to the original mnemonic seed. + """ + # 24-word seed, null bytes at the front + entropy = b'\x00' + os.urandom(31) + run_encode_decode_test(entropy, mnemonic_length=24, qr_type=QRType.COMPACTSEEDQR) + + # 24-word seed, null bytes in the middle + entropy = os.urandom(10) + b'\x00' + os.urandom(21) + run_encode_decode_test(entropy, mnemonic_length=24, qr_type=QRType.COMPACTSEEDQR) + + # 24-word seed, null bytes at the end + entropy = os.urandom(31) + b'\x00' + run_encode_decode_test(entropy, mnemonic_length=24, qr_type=QRType.COMPACTSEEDQR) + + # 12-word seed, null bytes at the beginning + entropy = b'\x00' + os.urandom(15) + run_encode_decode_test(entropy, mnemonic_length=12, qr_type=QRType.COMPACTSEEDQR) + + # 12-word seed, null bytes in the middle + entropy = os.urandom(5) + b'\x00' + os.urandom(10) + run_encode_decode_test(entropy, mnemonic_length=12, qr_type=QRType.COMPACTSEEDQR) + + # 12-word seed, null bytes at the end + entropy = os.urandom(15) + b'\x00' + run_encode_decode_test(entropy, mnemonic_length=12, qr_type=QRType.COMPACTSEEDQR)