Browse Source

Fix UR2 decoding/encoding to work with crypto-psbt type

Two issues:
- The UR2 sampler function was expecting the '1-3' indicator, and this is not included if there is a single UR fragment. Changed code to try to parse instead of relying on a complex regex.
- Fixed a similar issue with the UR1 sampler, but it can be simpler since we've already ruled out UR2.
- When sending the UR2 response, it always used "bytes" as the prefix.  Now if it receives 'crypto-psbt', it responds with 'crypto-psbt'.
- Fixed is_ut_type() utility function which was incorrect
- All these fixes allow the code to work with BlueWallet's new UR2 support, but also stay compatible with older BlueWallet versions that only support UR1.
dev-1.0.5
Ken Carpenter 4 years ago
parent
commit
0a37de6e6d
  1. 6
      ports/stm32/boards/Passport/modules/auth.py
  2. 1
      ports/stm32/boards/Passport/modules/common.py
  3. 2
      ports/stm32/boards/Passport/modules/data_codecs/data_decoder.py
  4. 3
      ports/stm32/boards/Passport/modules/data_codecs/qr_codec.py
  5. 6
      ports/stm32/boards/Passport/modules/data_codecs/ur1_codec.py
  6. 21
      ports/stm32/boards/Passport/modules/data_codecs/ur2_codec.py
  7. 10
      ports/stm32/boards/Passport/modules/ur2/ur_decoder.py
  8. 14
      ports/stm32/boards/Passport/modules/ur2/utils.py
  9. 6
      ports/stm32/boards/Passport/modules/ux.py

6
ports/stm32/boards/Passport/modules/auth.py

@ -905,7 +905,7 @@ async def sign_psbt_buf(psbt_buf):
# Create a new BytesIO() to hold the result
async def done(psbt):
from common import system, last_scanned_qr_type
from common import system, last_scanned_qr_type, last_scanned_ur_prefix
system.hide_busy_bar()
signed_bytes = None
with BytesIO() as bfd:
@ -928,7 +928,9 @@ async def sign_psbt_buf(psbt_buf):
# print('last_scanned_qr_type={}'.format(last_scanned_qr_type))
# Text format for UR1, but binary for UR2
o = DisplayURCode('Signed Txn', signed_bytes if last_scanned_qr_type == QRType.UR2 else signed_hex, last_scanned_qr_type or QRType.UR2, None, is_binary=True)
# def __init__(self, title, qr_text, qr_type, qr_args=None, msg=None, left_btn='DONE', right_btn='RESIZE', is_binary=False):
o = DisplayURCode('Signed Txn', signed_bytes if last_scanned_qr_type == QRType.UR2 else signed_hex, last_scanned_qr_type or QRType.UR2, {'prefix': last_scanned_ur_prefix }, is_binary=True)
result = await o.interact_bare()
UserAuthorizedAction.cleanup()

1
ports/stm32/boards/Passport/modules/common.py

@ -66,3 +66,4 @@ is_new_wallet_a_duplicate = False
# The QRTYpe of the last QR code that was scanned
last_scanned_qr_type = None
last_scanned_ur_prefix = None

2
ports/stm32/boards/Passport/modules/data_codecs/data_decoder.py

@ -29,7 +29,7 @@ class DataDecoder:
def get_error(self):
return None
def get_type(self):
def get_ur_prefix(self):
return None
def decode(self):

3
ports/stm32/boards/Passport/modules/data_codecs/qr_codec.py

@ -30,6 +30,9 @@ class QRDecoder(DataDecoder):
def get_error(self):
return None
def get_ur_prefix(self):
return None
def decode(self):
return self.data

6
ports/stm32/boards/Passport/modules/data_codecs/ur1_codec.py

@ -44,8 +44,8 @@ class UR1Decoder(DataDecoder):
def get_error(self):
return self.error
def get_type(self):
return self.decoder.expected_type()
def get_ur_prefix(self):
return 'bytes' # TODO: Get the type from the UR1 decoder
def decode(self):
from common import system
@ -112,7 +112,7 @@ class UR1Sampler(DataSampler):
# Return True if it matches or False if not
@classmethod
def sample(cls, data):
r = re.compile('^ur:bytes/(\d)+of(\d)+/')
r = re.compile('^ur:bytes/') # Don't look for the n of m count anymore
m = r.match(data.lower())
return m != None

21
ports/stm32/boards/Passport/modules/data_codecs/ur2_codec.py

@ -49,7 +49,7 @@ class UR2Decoder(DataDecoder):
else:
return None
def get_type(self):
def get_ur_prefix(self):
return self.decoder.expected_type()
def decode(self):
@ -69,9 +69,13 @@ class UR2Decoder(DataDecoder):
return QRType.UR2
class UR2Encoder(DataEncoder):
def __init__(self, _args):
def __init__(self, args):
self.qr_sizes = [280, 100, 70]
self.type = None
if isinstance(args, dict):
self.prefix = args['prefix'] or 'bytes'
else:
self.prefix = 'bytes'
def get_num_supported_sizes(self):
return len(self.qr_sizes)
@ -87,7 +91,7 @@ class UR2Encoder(DataEncoder):
# print('UR2: data={}'.format(to_str(data)))
encoder.encodeBytes(data)
# TODO: Need to change this interface most likely to allow for different types like crypto-psbt
ur_obj = UR("bytes", encoder.get_bytes())
ur_obj = UR(self.prefix, encoder.get_bytes())
self.ur_encoder = UREncoder(ur_obj, max_fragment_len)
# UR2.0's next_part() returns the initial pieces split into max_fragment_len bytes, but then switches over to
@ -104,9 +108,14 @@ class UR2Sampler(DataSampler):
# Return True if it matches or False if not
@classmethod
def sample(cls, data):
r = re.compile('^ur:[a-z\d-]+\/(\d)+-(\d)+\/')
m = r.match(data.lower())
return m != None
try:
# Rather than try a complex regex here, we just let the decoder try to decode and if it fails.
# it must not be UR2.
decoder = URDecoder()
result = decoder.receive_part(data)
return result
except e:
return False
# Number of bytes required to successfully recognize this format
@classmethod

10
ports/stm32/boards/Passport/modules/ur2/ur_decoder.py

@ -34,7 +34,7 @@ class InvalidFragment(Exception):
class URDecoder:
def __init__(self):
self.fountain_decoder = FountainDecoder()
self.expected_type = None
self._expected_type = None
self.result = None
@staticmethod
@ -93,13 +93,13 @@ class URDecoder:
raise InvalidSequenceComponent()
def validate_part(self, type):
if self.expected_type == None:
if self._expected_type == None:
if not is_ur_type(type):
return False
self.expected_type = type
self._expected_type = type
return True
else:
return type == self.expected_type
return type == self._expected_type
def receive_part(self, str):
try:
@ -148,7 +148,7 @@ class URDecoder:
return False
def expected_type(self):
return self.expected_type
return self._expected_type
def expected_part_count(self):
return self.fountain_decoder.expected_part_count()

14
ports/stm32/boards/Passport/modules/ur2/utils.py

@ -33,14 +33,12 @@ def string_to_bytes(s):
return bytes(s, 'utf8')
def is_ur_type(ch):
if 'a' <= ch and ch <= 'z':
return True
if '0' <= ch and ch <= '9':
return True
if ch == '-':
return True
return False
def is_ur_type(type):
for ch in type:
if not (ch >= 'a' and ch <= 'z') and not(ch >= '0' and ch <= '9') and ch != '-':
return False
return True
def partition(s, n):

6
ports/stm32/boards/Passport/modules/ux.py

@ -1180,9 +1180,9 @@ class DisplayURCode(UserInteraction):
return 250
def render_qr(self, data):
from utils import imported
from utils import imported, bytes_to_hex_str
# print('data: {}'.format(data))
# print('data: {}'.format(bytes_to_hex_str(data)))
if self.last_render_id != self.render_id:
self.last_render_id = self.render_id
@ -1488,7 +1488,9 @@ async def ux_scan_qr_code(title):
# Set the last QRType so that signed transactions know what to encode as
common.last_scanned_qr_type = qr_decoder.get_data_format()
common.last_scanned_ur_prefix = qr_decoder.get_ur_prefix()
# print('common.last_scanned_qr_type={}'.format(common.last_scanned_qr_type))
# print('common.last_scanned_ur_prefix={}'.format(common.last_scanned_ur_prefix))
break
progress = '{} OF {}'.format(qr_decoder.received_parts(), qr_decoder.total_parts())

Loading…
Cancel
Save