def doit(accept=True, in_psbt=None, finalize=False, accept_ms_import=False, expect_txn=True): if accept_ms_import: # XXX would be better to do cap_story here, but that would limit test to simulator need_keypress('y') time.sleep(0.050) if accept != None: need_keypress('y' if accept else 'x', timeout=None) if accept == False: with pytest.raises(CCUserRefused): done = None while done == None: time.sleep(0.050) done = dev.send_recv(CCProtocolPacker.get_signed_txn(), timeout=None) return else: done = None while done == None: time.sleep(0.00) done = dev.send_recv(CCProtocolPacker.get_signed_txn(), timeout=None) assert len(done) == 2 resp_len, chk = done psbt_out = dev.download_file(resp_len, chk) if not expect_txn: # skip checks; it's text return psbt_out if not finalize: if in_psbt: from psbt import BasicPSBT assert BasicPSBT().parse(in_psbt) != None else: from pycoin.tx.Tx import Tx # parse it res = psbt_out assert res[0:4] != b'psbt', 'still a PSBT, but asked for finalize' t = Tx.from_bin(res) assert t.version in [1, 2] return psbt_out
def sign_transaction(psbt_in, psbt_out=None, verbose=False, b64_mode=False, hex_mode=False, finalize=False, visualize=False, signed=False): "Approve a spending transaction by signing it on Coldcard" dev = ColdcardDevice(sn=force_serial) dev.check_mitm() # Handle non-binary encodings, and incorrect files. taste = psbt_in.read(10) psbt_in.seek(0) if taste == b'70736274ff' or taste == b'70736274FF': # Looks hex encoded; make into binary again hx = ''.join(re.findall(r'[0-9a-fA-F]*', psbt_in.read().decode('ascii'))) psbt_in = io.BytesIO(a2b_hex(hx)) elif taste[0:6] == b'cHNidP': # Base64 encoded input psbt_in = io.BytesIO(b64decode(psbt_in.read())) elif taste[0:5] != b'psbt\xff': click.echo("File doesn't have PSBT magic number at start.") sys.exit(1) # upload the transaction txn_len, sha = real_file_upload(psbt_in, dev=dev) flags = 0x0 if visualize or signed: flags |= STXN_VISUALIZE if signed: flags |= STXN_SIGNED elif finalize: flags |= STXN_FINALIZE # start the signing process ok = dev.send_recv(CCProtocolPacker.sign_transaction(txn_len, sha, flags=flags), timeout=None) assert ok == None # errors will raise here, no need for error display result, _ = wait_and_download(dev, CCProtocolPacker.get_signed_txn(), 1) # If 'finalize' is set, we are outputing a bitcoin transaction, # ready for the p2p network. If the CC wasn't able to finalize it, # an exception would have occured. Most people will want hex here, but # resisting the urge to force it. if visualize: if psbt_out: psbt_out.write(result) else: click.echo(result, nl=False) else: # save it if hex_mode: result = b2a_hex(result) elif b64_mode or (not psbt_out and os.isatty(0)): result = b64encode(result) if psbt_out: psbt_out.write(result) else: click.echo(result)
def sign_tx(self, tx): self.device.check_mitm() # Get psbt in hex and then make binary fd = io.BytesIO(base64.b64decode(tx.serialize())) # learn size (portable way) offset = 0 sz = fd.seek(0, 2) fd.seek(0) left = sz chk = sha256() for pos in range(0, sz, MAX_BLK_LEN): here = fd.read(min(MAX_BLK_LEN, left)) if not here: break left -= len(here) result = self.device.send_recv( CCProtocolPacker.upload(pos, sz, here)) assert result == pos chk.update(here) # do a verify expect = chk.digest() result = self.device.send_recv(CCProtocolPacker.sha256()) assert len(result) == 32 if result != expect: raise ValueError("Wrong checksum:\nexpect: %s\n got: %s" % (b2a_hex(expect).decode('ascii'), b2a_hex(result).decode('ascii'))) # start the signing process ok = self.device.send_recv(CCProtocolPacker.sign_transaction( sz, expect), timeout=None) assert ok == None if self.device.is_simulator: self.device.send_recv(CCProtocolPacker.sim_keypress(b'y')) print("Waiting for OK on the Coldcard...") while 1: time.sleep(0.250) done = self.device.send_recv(CCProtocolPacker.get_signed_txn(), timeout=None) if done == None: continue break if len(done) != 2: raise ValueError('Failed: %r' % done) result_len, result_sha = done result = self.device.download_file(result_len, result_sha, file_number=1) return {'psbt': base64.b64encode(result).decode()}
async def sign_psbt(self, data, finalize=False, flags=0x0): # upload it first async with self.sign_lock: sz, chk = self.dev.upload_file(data) assert chk == a2b_hex(STATUS.psbt_hash) await self.send_recv( CCProtocolPacker.sign_transaction(sz, chk, finalize, flags)) # wait for it to finish return await self.wait_and_download( CCProtocolPacker.get_signed_txn())
def sign_transaction(psbt_in, psbt_out, verbose=False, hex_mode=False, finalize=True): "Approve a spending transaction (by signing it on Coldcard)" dev = ColdcardDevice(sn=force_serial) dev.check_mitm() # not enforcing policy here on msg contents, so we can define that on product taste = psbt_in.read(10) psbt_in.seek(0) if taste == b'70736274ff': # hex encoded; make binary psbt_in = io.BytesIO(a2b_hex(psbt_in.read())) hex_mode = True elif taste[0:5] != b'psbt\xff': click.echo("File doesn't have PSBT magic number at start.") sys.exit(1) # upload the transaction txn_len, sha = real_file_upload(psbt_in, dev=dev) # start the signing process ok = dev.send_recv(CCProtocolPacker.sign_transaction(txn_len, sha), timeout=None) assert ok == None result, _ = wait_and_download(dev, CCProtocolPacker.get_signed_txn(), 1) if finalize: # assume(?) transaction is completely signed, and output the # bitcoin transaction to be sent. # XXX maybe do this on embedded side, when txn is final? # XXX otherwise, need to parse PSBT and also handle combining properly pass # save it psbt_out.write(b2a_hex(result) if hex_mode else result)
def sign_transaction_poll(self): # poll device... if user has approved, will get tuple: (legnth, checksum) else None return self.dev.send_recv(CCProtocolPacker.get_signed_txn(), timeout=None)
def sign_transaction_poll(self): # poll device... if user has approved, will get tuple: (legnth, checksum) else None return self.dev.send_recv(CCProtocolPacker.get_signed_txn(), timeout=None)