async def interact(self): # Prompt user w/ details and get approval from main import dis # step 1: parse PSBT from sflash into in-memory objects. dis.fullscreen("Validating...") try: with SFFile(TXN_INPUT_OFFSET, length=self.psbt_len) as fd: self.psbt = psbtObject.read_psbt(fd) except BaseException as exc: if isinstance(exc, MemoryError): msg = "Transaction is too complex" exc = None else: msg = "PSBT parse failed" return await self.failure(msg, exc) # Do some analysis/ validation try: await self.psbt.validate() # might do UX: accept multisig import self.psbt.consider_inputs() self.psbt.consider_keys() self.psbt.consider_outputs() except FraudulentChangeOutput as exc: print('FraudulentChangeOutput: ' + exc.args[0]) return await self.failure(exc.args[0], title='Change Fraud') except FatalPSBTIssue as exc: print('FatalPSBTIssue: ' + exc.args[0]) return await self.failure(exc.args[0]) except BaseException as exc: del self.psbt gc.collect() if isinstance(exc, MemoryError): msg = "Transaction is too complex" exc = None else: msg = "Invalid PSBT" return await self.failure(msg, exc) # step 2: figure out what we are approving, so we can get sign-off # - outputs, amounts # - fee # # notes: # - try to handle lots of outputs # - cannot calc fee as sat/byte, only as percent # - somethings are 'warnings': # - fee too big # - inputs we can't sign (no key) # try: msg = uio.StringIO() # mention warning at top wl= len(self.psbt.warnings) if wl == 1: msg.write('(1 warning below)\n\n') elif wl >= 2: msg.write('(%d warnings below)\n\n' % wl) self.output_summary_text(msg) gc.collect() fee = self.psbt.calculate_fee() if fee is not None: msg.write("\nNetwork fee:\n%s %s\n" % self.chain.render_value(fee)) if self.psbt.warnings: msg.write('\n---WARNING---\n\n') for label,m in self.psbt.warnings: msg.write('- %s: %s\n\n' % (label, m)) msg.write("\nPress OK to approve and sign transaction. X to abort.") ch = await ux_show_story(msg, title="OK TO SEND?") except MemoryError: # recovery? maybe. try: del self.psbt del msg except: pass # might be NameError since we don't know how far we got gc.collect() msg = "Transaction is too complex" return await self.failure(msg) if ch != 'y': # they don't want to! self.refused = True await ux_dramatic_pause("Refused.", 1) del self.psbt self.done() return # do the actual signing. try: gc.collect() self.psbt.sign_it() except FraudulentChangeOutput as exc: return await self.failure(exc.args[0], title='Change Fraud') except MemoryError: msg = "Transaction is too complex" return await self.failure(msg) except BaseException as exc: return await self.failure("Signing failed late", exc) if self.approved_cb: # for micro sd case await self.approved_cb(self.psbt) self.done() return try: # re-serialize the PSBT back out with SFFile(TXN_OUTPUT_OFFSET, max_size=MAX_TXN_LEN, message="Saving...") as fd: await fd.erase() if self.do_finalize: self.psbt.finalize(fd) else: self.psbt.serialize(fd) self.result = (fd.tell(), fd.checksum.digest()) self.done() except BaseException as exc: return await self.failure("PSBT output failed", exc)
here = orig.read(256) if not here: break if here[0:10] == b'70736274ff': is_hex = True if is_hex: here = a2b_hex(here) wr_fd.write(here) tl += len(here) from psbt import psbtObject, FatalPSBTIssue rd_fd = SFFile(0, tl) obj = psbtObject.read_psbt(rd_fd) # all these trival test cases now fail validation for various reasons... try: obj.validate() print("should fail") except AssertionError: pass except FatalPSBTIssue: pass obj.serialize(out_fd) out_tl = out_fd.tell() # copy back into MacOS filesystem with open('readback.psbt', 'wb') as rb:
from uio import BytesIO from sffile import SFFile # NOTE: not a psbt, just a txn # - 2 ins, 2 outs unsigned = a2b_hex('0100000002fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f0000000000eeffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac11000000') fd = SFFile(0, max_size=65536) list(fd.erase()) fd.write(b'psbt\xff\x01\x00' + bytes([len(unsigned)]) + unsigned + (b'\0'*8)) psbt_len = fd.tell() rfd = SFFile(0, psbt_len) p = psbtObject.read_psbt(rfd) #p.validate() # failed because no subpaths; don't care amt = 600000000 sc = a2b_hex('1976a9141d0f172a0ecb48aee1be1f2687d2963ae33f71a188ac') outpt2 = a2b_hex('ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff') replacement = CTxIn() replacement.deserialize(BytesIO(outpt2)) digest = p.make_txn_segwit_sighash(0, replacement, amt, sc, 0x01) print('Got: ' + b2a_hex(digest).decode('ascii')) assert digest == a2b_hex('c37af31116d1b27caf68aae9e3ac82f1477929014d5b917657d0eb49478cb670')
async def interact(self): # Prompt user w/ details and get approval from glob import dis, hsm_active # step 1: parse PSBT from sflash into in-memory objects. try: dis.fullscreen("Reading...") with SFFile(TXN_INPUT_OFFSET, length=self.psbt_len) as fd: self.psbt = psbtObject.read_psbt(fd) except BaseException as exc: if isinstance(exc, MemoryError): msg = "Transaction is too complex" exc = None else: msg = "PSBT parse failed" return await self.failure(msg, exc) dis.fullscreen("Validating...") # Do some analysis/ validation try: await self.psbt.validate() # might do UX: accept multisig import self.psbt.consider_inputs() self.psbt.consider_keys() self.psbt.consider_outputs() except FraudulentChangeOutput as exc: print('FraudulentChangeOutput: ' + exc.args[0]) return await self.failure(exc.args[0], title='Change Fraud') except FatalPSBTIssue as exc: print('FatalPSBTIssue: ' + exc.args[0]) return await self.failure(exc.args[0]) except BaseException as exc: del self.psbt gc.collect() if isinstance(exc, MemoryError): msg = "Transaction is too complex" exc = None else: msg = "Invalid PSBT" return await self.failure(msg, exc) # step 2: figure out what we are approving, so we can get sign-off # - outputs, amounts # - fee # # notes: # - try to handle lots of outputs # - cannot calc fee as sat/byte, only as percent # - somethings are 'warnings': # - fee too big # - inputs we can't sign (no key) # try: msg = uio.StringIO() # mention warning at top wl= len(self.psbt.warnings) if wl == 1: msg.write('(1 warning below)\n\n') elif wl >= 2: msg.write('(%d warnings below)\n\n' % wl) self.output_summary_text(msg) gc.collect() fee = self.psbt.calculate_fee() if fee is not None: msg.write("\nNetwork fee:\n%s %s\n" % self.chain.render_value(fee)) # NEW: show where all the change outputs are going self.output_change_text(msg) gc.collect() if self.psbt.warnings: msg.write('\n---WARNING---\n\n') for label, m in self.psbt.warnings: msg.write('- %s: %s\n\n' % (label, m)) if self.do_visualize: # stop here and just return the text of approval message itself self.result = await self.save_visualization(msg, (self.stxn_flags & STXN_SIGNED)) del self.psbt self.done() return if not hsm_active: msg.write("\nPress OK to approve and sign transaction. X to abort.") ch = await ux_show_story(msg, title="OK TO SEND?") else: ch = await hsm_active.approve_transaction(self.psbt, self.psbt_sha, msg.getvalue()) dis.progress_bar(1) # finish the Validating... except MemoryError: # recovery? maybe. try: del self.psbt del msg except: pass # might be NameError since we don't know how far we got gc.collect() msg = "Transaction is too complex" return await self.failure(msg) if ch != 'y': # they don't want to! self.refused = True await ux_dramatic_pause("Refused.", 1) del self.psbt self.done() return # do the actual signing. try: dis.fullscreen('Wait...') gc.collect() # visible delay causes by this but also sign_it() below self.psbt.sign_it() except FraudulentChangeOutput as exc: return await self.failure(exc.args[0], title='Change Fraud') except MemoryError: msg = "Transaction is too complex" return await self.failure(msg) except BaseException as exc: return await self.failure("Signing failed late", exc) if self.approved_cb: # for micro sd case await self.approved_cb(self.psbt) self.done() return txid = None try: # re-serialize the PSBT back out with SFFile(TXN_OUTPUT_OFFSET, max_size=MAX_TXN_LEN, message="Saving...") as fd: await fd.erase() if self.do_finalize: txid = self.psbt.finalize(fd) else: self.psbt.serialize(fd) self.result = (fd.tell(), fd.checksum.digest()) self.done(redraw=(not txid)) except BaseException as exc: return await self.failure("PSBT output failed", exc) if self.do_finalize and txid and not hsm_active: # Show txid when we can; advisory # - maybe even as QR, hex-encoded in alnum mode tmsg = txid if version.has_fatram: tmsg += '\n\nPress 1 for QR Code of TXID.' ch = await ux_show_story(tmsg, "Final TXID", escape='1') if version.has_fatram and ch=='1': await show_qr_code(txid, True)
async def interact(self): # Prompt user w/ details and get approval from common import dis # step 1: parse PSBT from sflash into in-memory objects. dis.fullscreen("Validating...") if self.psbt == None: try: # Read TXN from SPI Flash (we put it there whether it came from a QR code or an SD card) with SFFile(TXN_INPUT_OFFSET, length=self.psbt_len) as fd: self.psbt = psbtObject.read_psbt(fd) except BaseException as exc: if isinstance(exc, MemoryError): msg = "Transaction is too complex" exc = None else: msg = "PSBT parse failed" return await self.failure(msg, exc) # Do some analysis/validation try: await self.psbt.validate() # might do UX: accept multisig import self.psbt.consider_inputs() self.psbt.consider_keys() self.psbt.consider_outputs() except FraudulentChangeOutput as exc: print('FraudulentChangeOutput: ' + exc.args[0]) return await self.failure(exc.args[0], title='Change Fraud') except FatalPSBTIssue as exc: print('FatalPSBTIssue: ' + exc.args[0]) return await self.failure(exc.args[0]) except BaseException as exc: del self.psbt gc.collect() if isinstance(exc, MemoryError): msg = "Transaction is too complex" exc = None else: msg = "Invalid PSBT" return await self.failure(msg, exc) # step 2: figure out what we are approving, so we can get sign-off # - outputs, amounts # - fee # # notes: # - try to handle lots of outputs # - cannot calc fee as sat/byte, only as percent # - somethings are 'warnings': # - fee too big # - inputs we can't sign (no key) # try: outputs = uio.StringIO() outputs.write('Amount:') first = True for idx, tx_out in self.psbt.output_iter(): outp = self.psbt.outputs[idx] if outp.is_change: continue if first: first = False else: outputs.write('\n') outputs.write(self.render_output(tx_out)) # print('total_out={} total_in={} change={}'.format=(self.psbt.total_value_out, self.psbt.total_value_in, self.psbt.total_value_in - self.psbt.total_value_out)) pages = [ {'title': 'Sign Txn', 'msg': outputs.getvalue(), 'center': True, 'center_vertically': True}, {'title': 'Sign Txn', 'msg': self.render_change_text(), 'center': True, 'center_vertically': True}, ] warnings = self.render_warnings() print('warnings = "{}"'.format(warnings)) if warnings != None: pages.append( {'title': 'Sign Txn', 'msg': warnings, 'center': True, 'center_vertically': True, 'right_btn': 'SIGN!'} ) if self.do_visualize: # stop here and just return the text of approval message itself self.result = await self.save_visualization(msg, (self.stxn_flags & STXN_SIGNED)) del self.psbt self.done() return result = await ux_show_story_sequence(pages) except MemoryError: # recovery? maybe. try: del self.psbt del msg except: pass # might be NameError since we don't know how far we got gc.collect() msg = "Transaction is too complex" return await self.failure(msg) if result != 'y': # User chose not to sign the transaction self.refused = True # TODO: ux_confirm() instead? # await ux_dramatic_pause("Refused.", 1) del self.psbt self.done() return # do the actual signing. try: gc.collect() self.psbt.sign_it() except FraudulentChangeOutput as exc: return await self.failure(exc.args[0], title='Change Fraud') except MemoryError: msg = "Transaction is too complex" return await self.failure(msg) except BaseException as exc: return await self.failure("Signing failed late", exc) if self.approved_cb: # for micro sd case await self.approved_cb(self.psbt) self.done() return try: # re-serialize the PSBT back out with SFFile(TXN_OUTPUT_OFFSET, max_size=MAX_TXN_LEN, message="Saving...") as fd: await fd.erase() if self.do_finalize: self.psbt.finalize(fd) else: self.psbt.serialize(fd) self.result = (fd.tell(), fd.checksum.digest()) self.done() except BaseException as exc: return await self.failure("PSBT output failed", exc)