# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC. # from main import dis from ubinascii import hexlify as b2a_hex RV.write(b2a_hex(dis.dis.buffer))
def gate(method, buf_io, arg2): # the "callgate" into the bootloader # - only spuratically implemented (say it like in _Clueless_) # - none of the error checking is repeated here # - not trying too hard to fake the hardware-related features if method == 0: # version string hc = b'2.0.0 time=20180220.092345 git=master@f8d1758' buf_io[0:len(hc)] = hc return len(hc) if method == 16: if len(buf_io) < 8: return ERANGE return pin_prefix(buf_io[0:arg2], buf_io) if method == 18: from sim_secel import pin_stuff return pin_stuff(arg2, buf_io) if method == 4: # control the green/red light global genuine_led if arg2 == 1: # clear it genuine_led = False led_pipe.write(bytes([0x10])) return 1 if genuine_led else 0 if method == 5: # are we a brick? No. return 0 if method == 6: # do we have 608? return ENOENT if not version.has_608 else 0 if method == 19: # bag number if arg2 == 0: buf_io[0:32] = b'CSIM0000' + b'\0' * (32 - 8) if arg2 == 1: # not supported: write return buf_io if method == 21: # high water mark if arg2 == 0: #buf_io[0:8] = b'\x18\x07\x11\x19S\x08\x00\x00' #buf_io[0:8] = b'!\x04)\x21\'"\x00\x00' buf_io[0:8] = b'!\x03)\x19\'"\x00\x00' #buf_io[0:8] = bytes(8) elif arg2 == 2: print("New highwater: %s" % b2a_hex(buf_io[0:8])) return 0 if method == 20: # read 608 config bytes (128) assert len(buf_io) == 128 buf_io[:] = b'\x01#\xbf\x0b\x00\x00`\x03CP,\xbf\xeeap\x00\xe1\x00a\x00\x00\x00\x8f-\x8f\x80\x8fC\xaf\x80\x00C\x00C\x8fG\xc3C\xc3C\xc7G\x00G\x00\x00\x8fM\x8fC\x00\x00\x00\x00\x1f\xff\x00\x1a\x00\x1a\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xea\xff\x02\x15\x00\x00\x00\x00<\x00\\\x00\xbc\x01\xfc\x01\xbc\x01\x9c\x01\x9c\x01\xfc\x01\xdc\x03\xdc\x03\xdc\x07\x9c\x01<\x00\xfc\x01\xdc\x01<\x00' return 0 return ENOENT
async def import_xprv(*A): # read an XPRV from a text file and use it. import tcc, chains, ure from main import pa from stash import SecretStash from ubinascii import hexlify as b2a_hex from backups import restore_from_dict assert pa.is_secret_blank() # "must not have secret" def contains_xprv(fname): # just check if likely to be valid; not full check try: with open(fname, 'rt') as fd: for ln in fd: # match tprv and xprv, plus y/zprv etc if 'prv' in ln: return True return False except OSError: # directories? return False # pick a likely-looking file. fn = await file_picker('Select file containing the XPRV to be imported.', min_size=50, max_size=2000, taster=contains_xprv) if not fn: return node, chain, addr_fmt = None, None, None # open file and do it pat = ure.compile(r'.prv[A-Za-z0-9]+') with CardSlot() as card: with open(fn, 'rt') as fd: for ln in fd.readlines(): if 'prv' not in ln: continue found = pat.search(ln) if not found: continue found = found.group(0) for ch in chains.AllChains: for kk in ch.slip132: if found[0] == ch.slip132[kk].hint: try: node = tcc.bip32.deserialize( found, ch.slip132[kk].pub, ch.slip132[kk].priv) chain = ch addr_fmt = kk break except ValueError: pass if node: break if not node: # unable await ux_show_story('''\ Sorry, wasn't able to find an extended private key to import. It should be at \ the start of a line, and probably starts with "xprv".''', title="FAILED") return # encode it in our style d = dict(chain=chain.ctype, raw_secret=b2a_hex(SecretStash.encode(xprv=node))) node.blank() # TODO: capture the address format implied by SLIP32 version bytes #addr_fmt = # restore as if it was a backup (code reuse) await restore_from_dict(d)
async def clone_start(*a): # Begins cloning process, on target device. from files import CardSlot, CardMissingError ch = await ux_show_story( '''Insert a MicroSD card and press OK to start. A small \ file with an ephemeral public key will be written.''') if ch != 'y': return # pick a random key pair, just for this cloning session pair = ngu.secp256k1.keypair() my_pubkey = pair.pubkey().to_bytes(False) # write to SD Card, fixed filename for ease of use try: with CardSlot() as card: fname, nice = card.pick_filename('ccbk-start.json', overwrite=True) with open(fname, 'wb') as fd: fd.write(ujson.dumps(dict(pubkey=b2a_hex(my_pubkey)))) except CardMissingError: await needs_microsd() return except Exception as e: await ux_show_story('Error: ' + str(e)) return # Wait for incoming clone file, allow retries ch = await ux_show_story( '''Keep power on this Coldcard, and take MicroSD card \ to source Coldcard. Select Advanced > MicroSD > Clone Coldcard to write to card. Bring that card \ back and press OK to complete clone process.''') while 1: if ch != 'y': # try to clean up, but card probably not there? No errors. try: with CardSlot() as card: uos.remove(fname) except: pass await ux_dramatic_pause('Aborted.', 2) return # Hopefully we have a suitable 7z file now. Pubkey in the filename incoming = None try: with CardSlot() as card: for path in card.get_paths(): for fn, ftype, *var in uos.ilistdir(path): if fn.endswith('-ccbk.7z'): incoming = path + '/' + fn his_pubkey = a2b_hex(fn[0:66]) assert len(his_pubkey) == 33 assert 2 <= his_pubkey[0] <= 3 break except CardMissingError: await needs_microsd() continue except Exception as e: pass if incoming: break ch = await ux_show_story( "Clone file not found. OK to try again, X to stop.") # calculate point session_key = pair.ecdh_multiply(his_pubkey) # "password" is that hex value words = [b2a_hex(session_key).decode()] def delme(xfn): # Callback to delete file after its read; could still fail but # need to start over in that case anyway. uos.remove(xfn) uos.remove(fname) # ccbk-start.json # this will reset in successful case, no return (but delme is called) prob = await restore_complete_doit(incoming, words, file_cleanup=delme) if prob: await ux_show_story(prob, title='FAILED')
def bytes_to_hex_str(s): return str(b2a_hex(s), 'ascii')
('bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx', '5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6', 1), ('BC1SW50QA3JX3S', '6002751e', 16), ('bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj', '5210751e76e8199196d454941c45d1b3a323', 2), ('tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy', '0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433', 0), ] for addr, expect, v in decode: hrp, version, data = bech32_decode(addr) assert version == v, (addr, version, v) assert hrp == addr.lower()[0:2] assert expect.endswith(b2a_hex(data)), (expect, data) #print("%s ok" % addr) # some bad checksums chk = [ 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5', 'li1dgmt3', 'de1lg7wt\xff', 'A1G7SGD8', ] for v in chk: try: hrp, version, data = bech32_decode(v) assert False, ("%s => %s,%d,%r" % (v, hrp, version, data))
def write(self, b): self.checksum.update(b) self.pos += len(b) self.fd.write(b2a_hex(b))
def go(): # see bootloader/data/ae_layout.h c1 = bytes([ 0xe1, 0x00, 0x55, 0x00, 0x00, 0x00, 0x8f, 0x2d, 0x8f, 0x80, \ 0x8f, 0x43, 0xc3, 0x43, 0x00, 0x43, 0x8f, 0x46, 0xc6, 0x46, \ 0x00, 0x46, 0x8f, 0x49, 0xc9, 0x49, 0x8f, 0x4b, 0xcb, 0x4b, \ 0x8f, 0x4d, 0xc1, 0x41, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, \ 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, \ 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff \ ]) c2 = bytes([ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x5c, 0x00, \ 0xbc, 0x01, 0xfc, 0x01, 0xdc, 0x03, 0x9c, 0x01, 0xfc, 0x01, \ 0xdc, 0x06, 0x9c, 0x01, 0xfc, 0x01, 0xdc, 0x09, 0xfc, 0x01, \ 0xdc, 0x0b, 0xfc, 0x01, 0xdc, 0x01, 0x3c, 0x00 \ ]) if not ae.is_config_locked(): ae.data[16:84] = c1 ae.data[90:128] = c2 assert len(ae.data) == 128 b4 = bytes(ae.data) ae.write() ae.read() assert b4 == ae.data print("locking configzone") ae.LOCK(is_config=1) ae.reset_watchdog() ae.read() # assume all slots are blank ae.assume_data_blank() if not ae.is_slot_locked(KN_pairing): assert len(pairing_key) == 32 ae.write_data_slot(KN_pairing, pairing_key) # cannot lock this slot, or else we can't burn it via DeriveKey #ae.LOCK(data=pairing_key+FF4, slot_num=KN_pairing, datazone=True) else: ae.d_slot[KN_pairing] = pairing_key+FF4 # check pairing key works ae.reset_watchdog() ae.do_checkmac(KN_pairing, pairing_key) print("checkmac pairing works") if not ae.is_slot_locked(KN_words): assert len(words_key) == 32 ae.write_data_slot(KN_words, words_key) ae.do_checkmac(KN_words, words_key) # check write # always lock it ae.LOCK(data=words_key+b'\xff\xff\xff\xff', slot_num=KN_words, datazone=True) else: ae.d_slot[KN_words] = words_key+FF4 # need both keys to be able to do this! ae.reset_watchdog() ae.do_checkmac(KN_pairing, pairing_key) ae.do_checkmac(KN_words, words_key) # will fail if we didn't just auth w/ pairing key print("checkmac words works") ae.reset_watchdog() ae.do_checkmac(KN_pairing, pairing_key) hm = ae.hmac(KN_words, b'0'*32, diverse=False) # production: will be diverse mode assert hm == a2b_hex('aadec702b4855df0c1838a48978d56a65e5871f291e835d0f833aa2fdfd30290'), b2a_hex(hm) print("HMAC(words, '0'*32) works") # Set PIN's to something known. # simple non-encrypted write can work while data unlocked if not ae.is_data_locked(): for idx in range(4): ae.reset_watchdog() ae.write_data_slot(KN_pins[idx], BLANK) ae.write_data_slot(KN_secrets[idx], BLANK) if idx < len(KN_lastgood): ae.write_data_slot(KN_lastgood[idx], BLANK) ae.reset_watchdog() ae.write_data_slot(KN_brickme, BLANK) ae.write_data_slot(KN_firmware, BLANK) print("Locking data zone") ae.LOCK(datazone=True, ecc_slots=[], no_crc=1) ae.reset_watchdog() ae.read() print("Success")
def pin_stuff(submethod, buf_io): from pincodes import (PIN_ATTEMPT_SIZE, PIN_ATTEMPT_FMT_V1, PA_ZERO_SECRET, PIN_ATTEMPT_SIZE_V1, CHANGE_LS_OFFSET, PA_SUCCESSFUL, PA_IS_BLANK, PA_HAS_DURESS, PA_HAS_BRICKME, CHANGE_WALLET_PIN, CHANGE_DURESS_PIN, CHANGE_BRICKME_PIN, CHANGE_SECRET, CHANGE_DURESS_SECRET, CHANGE_SECONDARY_WALLET_PIN ) if len(buf_io) != (PIN_ATTEMPT_SIZE if version.has_608 else PIN_ATTEMPT_SIZE_V1): return ERANGE global SECRETS (magic, is_secondary, pin, pin_len, delay_achieved, delay_required, num_fails, attempts_left, state_flags, private_state, hmac, change_flags, old_pin, old_pin_len, new_pin, new_pin_len, secret) = ustruct.unpack_from(PIN_ATTEMPT_FMT_V1, buf_io) # NOTE: ignoring mk2 additions for now, we have no need for it. # NOTE: using strings here, not bytes; real bootrom uses bytes pin = pin[0:pin_len].decode() old_pin = old_pin[0:old_pin_len].decode() new_pin = new_pin[0:new_pin_len].decode() kk = '_pin1' if not is_secondary else '_pin2' if submethod == 0: # setup state_flags = (PA_SUCCESSFUL | PA_IS_BLANK) if not SECRETS.get(kk, False) else 0 if pin.startswith('77'): delay_required = int(pin.split('-')[1]) * 2 num_fails = 3 if pin.startswith('88'): attempts_left = 2 num_fails = 11 if version.has_608: attempts_left = 13 num_fails = 0 if 0: # XXX test num_fails = 10 attempts_left = 3 elif submethod == 1: # delay time.sleep(0.500) delay_achieved += 1 elif submethod == 2: # Login expect = SECRETS.get(kk, '') if pin == expect: state_flags = PA_SUCCESSFUL ts = a2b_hex(SECRETS.get(kk+'_secret', '00'*72)) elif pin == SECRETS.get(kk + '_duress', None): state_flags = PA_SUCCESSFUL ts = a2b_hex(SECRETS.get(kk+'_duress_secret', '00'*72)) else: if version.has_608: num_fails += 1 attempts_left -= 1 else: state_flags = 0 return EPIN_AUTH_FAIL time.sleep(0.5) if ts == b'\0'*72: state_flags |= PA_ZERO_SECRET if kk+'_duress' in SECRETS: state_flags |= PA_HAS_DURESS if kk+'_brickme' in SECRETS: state_flags |= PA_HAS_BRICKME del ts elif submethod == 3: # CHANGE pin and/or wallet secrets cf = change_flags # NOTE: this logic copied from real deal # must be here to do something. if cf == 0: return EPIN_RANGE_ERR; if cf & CHANGE_BRICKME_PIN: if is_secondary: # only main PIN holder can define brickme PIN return EPIN_PRIMARY_ONLY if cf != CHANGE_BRICKME_PIN: # only pin can be changed, nothing else. return EPIN_BAD_REQUEST if (cf & CHANGE_DURESS_SECRET) and (cf & CHANGE_SECRET): # can't change two secrets at once. return EPIN_BAD_REQUEST if (cf & CHANGE_SECONDARY_WALLET_PIN): if is_secondary: # only main user uses this call return EPIN_BAD_REQUEST; if (cf != CHANGE_SECONDARY_WALLET_PIN): # only changing PIN, no secret-setting return EPIN_BAD_REQUEST; kk = '_pin2' # what PIN will we change pk = None if change_flags & (CHANGE_WALLET_PIN | CHANGE_SECONDARY_WALLET_PIN): pk = kk if change_flags & CHANGE_DURESS_PIN: pk = kk+'_duress' if change_flags & CHANGE_BRICKME_PIN: pk = kk+'_brickme' if pin == SECRETS.get(kk + '_duress', None): # acting duress mode... let them only change duress pin if pk == kk: pk = kk+'_duress' else: return EPIN_OLD_AUTH_FAIL if pk != None: # Must match old pin correctly, if it is defined. if SECRETS.get(pk, '') != old_pin: print("secel: wrong OLD pin (expect %s, got %s)" % (SECRETS[pk], old_pin)) return EPIN_OLD_AUTH_FAIL # make change SECRETS[pk] = new_pin if not new_pin and pk != kk: del SECRETS[pk] if change_flags & CHANGE_SECRET: SECRETS[kk+'_secret'] = str(b2a_hex(secret), 'ascii') if change_flags & CHANGE_DURESS_SECRET: SECRETS[kk+'_duress_secret'] = str(b2a_hex(secret), 'ascii') time.sleep(1.5) elif submethod == 4: # Fetch secrets duress_pin = SECRETS.get(kk+'_duress') if pin == duress_pin: secret = a2b_hex(SECRETS.get(kk+'_duress_secret', '00'*72)) else: # main/sec secrets expect = SECRETS.get(kk, '') if pin == expect: secret = a2b_hex(SECRETS.get(kk+'_secret', '00'*72)) else: return EPIN_AUTH_FAIL elif submethod == 5: # greenlight firmware from ckcc import genuine_led, led_pipe genuine_led = True led_pipe.write(b'\x01') elif submethod == 6: if not version.has_608: return ENOENT # long secret read/change. cf = change_flags assert CHANGE_LS_OFFSET == 0xf00 blk = (cf >> 8) & 0xf if blk > 13: return EPIN_RANGE_ERR off = blk * 32 if 'ls' not in SECRETS: SECRETS['ls'] = bytearray(416) if (cf & CHANGE_SECRET): SECRETS['ls'][off:off+32] = secret else: secret = SECRETS['ls'][off:off+32] else: # bogus submethod return ENOENT hmac = b'69'*16 ustruct.pack_into(PIN_ATTEMPT_FMT_V1, buf_io, 0, magic, is_secondary, pin.encode(), pin_len, delay_achieved, delay_required, num_fails, attempts_left, state_flags, private_state, hmac, change_flags, old_pin.encode(), old_pin_len, new_pin.encode(), new_pin_len, secret) return 0
# (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC. # # utils.py - Misc utils. My favourite kind of source file. # import gc, sys, ustruct, ngu from ubinascii import unhexlify as a2b_hex from ubinascii import hexlify as b2a_hex from ubinascii import a2b_base64, b2a_base64 from uhashlib import sha256 B2A = lambda x: str(b2a_hex(x), 'ascii') class imported: # Context manager that temporarily imports # a list of modules. # LATER: doubtful this saves any memory when all the code is frozen. def __init__(self, *modules): self.modules = modules def __enter__(self): # import everything required rv = tuple(__import__(n) for n in self.modules) return rv[0] if len(self.modules) == 1 else rv def __exit__(self, exc_type, exc_value, traceback): for n in self.modules: if n in sys.modules: del sys.modules[n]
try: import ngu from ubinascii import hexlify as b2a_hex sha512 = ngu.hash.sha512 hmac_sha1 = ngu.hmac.hmac_sha1 hmac_sha256 = ngu.hmac.hmac_sha256 hmac_sha512 = ngu.hmac.hmac_sha512 except ImportError: # desktop from binascii import b2a_hex from hashlib import sha256, sha1, sha512 from hmac import HMAC hmac_sha1 = lambda key, msg=None: HMAC(key, msg, sha1).digest() hmac_sha256 = lambda key, msg=None: HMAC(key, msg, sha256).digest() hmac_sha512 = lambda key, msg=None: HMAC(key, msg, sha512).digest() assert b2a_hex(hmac_sha1( b'ab' * 16, b'hello')) == b'6ffb8c2e3e85677f02913c480a44486018db0552' assert b2a_hex( hmac_sha256(b'ab' * 16, b'hello') ) == b'254ac1adfb14b1de23b5bfc5d1a7d2a0ca0ebcdc2676fed0b81b121912ce6a67' assert b2a_hex( hmac_sha512(b'ab' * 16, b'hello') ) == b'435c32af24b24c1c47257e5f055dccb743f732b0723a4ff014cdf0c9a8500b087e01a85033f0a109c5841c1362e783e0776e8e45f9c09c070025fa35fe9d8160' print("PASS - test_hmac")
def determine_my_signing_key(self, my_idx, utxo): # See what it takes to sign this particular input # - type of script # - which pubkey needed # - scriptSig value addr_type, addr_or_pubkey, addr_is_segwit = utxo.get_address() which_key = None self.is_multisig = False self.is_p2sh = False self.amount = utxo.nValue if addr_is_segwit and not self.is_segwit: self.is_segwit = True if addr_type == 'p2sh': # multisig input self.is_p2sh = True # we must have the redeem script already (else fail) if not self.redeem_script: raise AssertionError("missing redeem script for in #%d" % my_idx) redeem_script = self.get(self.redeem_script) self.scriptSig = ser_string(redeem_script) # new cheat: psbt creator probably telling us exactly what key # to use, by providing exactly one. This is ideal for p2sh wrapped p2pkh if len(self.subpaths) == 1: which_key, = self.subpaths.keys() else: # messy P2SH multisig guessing? ws = self.get(self.witness_script ) if self.witness_script else redeem_script for pubkey in self.subpaths: if pubkey in ws: # limitations: # - we could be holding multiple legs of the P2SH # - text match like this could be fooled w/ crafting which_key = pubkey break if not addr_is_segwit and \ len(redeem_script) == 22 and \ redeem_script[0] == 0 and redeem_script[1] == 20: # it's actually segwit p2pkh inside p2sh addr_type = 'p2wpkh-p2sh' addr = redeem_script[2:22] self.is_segwit = True else: # multiple keys involved, we probably can't do the finalize step self.is_multisig = True elif addr_type == 'p2pkh': # input is hash160 of a single public key self.scriptSig = utxo.scriptPubKey addr = addr_or_pubkey for pubkey in self.subpaths: if hash160(pubkey) == addr: which_key = pubkey break elif addr_type == 'p2pk': # input is single public key (less common) self.scriptSig = utxo.scriptPubKey assert len(addr_or_pubkey) == 33 if addr_or_pubkey in self.subpaths: which_key = addr_or_pubkey else: # we don't know how to "solve" this type of input pass if not which_key: print( "no key: input #%d: type=%s segwit=%d a_or_pk=%s scriptPubKey=%s" % (my_idx, addr_type, self.is_segwit, b2a_hex(addr_or_pubkey), b2a_hex(utxo.scriptPubKey))) self.required_key = which_key if self.is_segwit: if ('pkh' in addr_type): # This comment from <https://bitcoincore.org/en/segwit_wallet_dev/>: # # Please note that for a P2SH-P2WPKH, the scriptCode is always 26 # bytes including the leading size byte, as 0x1976a914{20-byte keyhash}88ac, # NOT the redeemScript nor scriptPubKey # # Also need this scriptCode for native segwit p2pkh # assert not self.is_multisig self.scriptCode = b'\x19\x76\xa9\x14' + addr + b'\x88\xac' elif not self.scriptCode: # Segwit P2SH segwit. We need the script! if not self.witness_script: raise AssertionError('Need witness script for input #%d' % my_idx) self.scriptCode = self.get(self.witness_script)
def update(self, d): print(b2a_hex(d), end=' ') self.rv.update(d)
async def approve_transaction(self, psbt, psbt_sha, story): # Approve or don't a transaction. Catch assertions and other # reasons for failing/rejecting into the log. # - return 'y' or 'x' chain = chains.current_chain() assert psbt_sha and len(psbt_sha) == 32 self.get_time_left() with AuditLogger('psbt', psbt_sha, self.never_log) as log: if self.must_log and log.is_unsaved: self.refuse(log, "Could not log details, and must_log is set") return 'x' log.info('Transaction signing requested:') log.info('SHA256(PSBT) = ' + b2a_hex(psbt_sha).decode('ascii')) log.info('-vvv-\n%s\n-^^^-' % story) # reset pending auth list and "consume" it now auth = self.pending_auth self.pending_auth = {} try: # do this super early so always cleared even if other issues local_ok = self.consume_local_code(psbt_sha) if not self.rules: raise ValueError("no txn signing allowed") # reject anything with warning, probably if psbt.warnings: if self.warnings_ok: log.info( "Txn has warnings, but policy is to accept anyway." ) else: raise ValueError("has %d warning(s)" % len(psbt.warnings)) # See who has entered creditials already (all must be valid). users = [] for u, (token, counter) in auth.items(): problem = Users.auth_okay(u, token, totp_time=counter, psbt_hash=psbt_sha) if problem: self.refuse( log, "User '%s' gave wrong auth value: %s" % (u, problem)) return 'x' users.append(u) # was right code provided locally? (also resets for next attempt) if local_ok: log.info("Local operator gave correct code.") if users: log.info("These users gave correct auth codes: " + ', '.join(users)) # Where is it going? total_out = 0 dests = [] for idx, tx_out in psbt.output_iter(): if not psbt.outputs[idx].is_change: total_out += tx_out.nValue dests.append(chain.render_address(tx_out.scriptPubKey)) # Pick a rule to apply to this specific txn reasons = [] for rule in self.rules: try: if rule.matches_transaction(psbt, users, total_out, dests, local_ok): break except BaseException as exc: # let's not share these details, except for debug; since # they are not errors, just picking best rule in priority order r = "rule #%d: %s" % (rule.index, str(exc) or problem_file_line(exc)) reasons.append(r) print(r) else: err = "Rejected: " + ', '.join(reasons) self.refuse(log, err) return 'x' if users: msg = ', '.join(auth.keys()) if local_ok: msg += ', and the local operator.' if msg else 'local operator' # looks good, do it self.approve(log, "Acceptable by rule #%d" % rule.index) if rule.per_period is not None: self.record_spend(rule, total_out) return 'y' except BaseException as exc: sys.print_exception(exc) err = "Rejected: " + (str(exc) or problem_file_line(exc)) self.refuse(log, err) return 'x'
def xfp2str(xfp): # Standardized way to show an xpub's fingerprint... it's a 4-byte string # and not really an integer. Used to show as '0x%08x' but that's wrong endian. return b2a_hex(ustruct.pack('<I', xfp)).decode().upper()
def hexdump(label, x): print(label + ASC(b2a_hex(x)) + (' len=%d' % len(x)))
def secel_dump(blk, rnd=None, which_nums=range(16)): ASC = lambda b: str(b, 'ascii') def hexdump(label, x): print(label + ASC(b2a_hex(x)) + (' len=%d' % len(x))) #hexdump('SN: ', blk[0:4]+blk[8:13]) hexdump('RevNum: ', blk[4:8]) # guessing this nibble in RevNum corresponds to chip 508a vs 608a print("Chip type: atecc%x08a" % ((blk[6] >> 4) & 0xf)) partno = ((blk[6] >> 4) & 0xf) assert partno in [5, 6] if partno == 6: print('AES_Enable = 0x%x' % (blk[13] & 0x1)) print('I2C_Enable = 0x%x' % (blk[14] & 0x1)) if blk[14] & 0x01 == 0x01: print('I2C_Address = 0x%x' % (blk[16] >> 1)) else: print('GPIO Mode = 0x%x' % (blk[16] & 0x3)) print('GPIO Default = 0x%x' % ((blk[16] >> 2) & 0x1)) print('GPIO Detect (vs authout) = 0x%x' % ((blk[16] >> 3) & 0x1)) print('GPIO SignalKey/KeyId = 0x%x' % ((blk[16] >> 4) & 0xf)) print('I2C_Address(sic) = 0x%x' % blk[16]) if partno == 5: print('OTPmode = 0x%x' % blk[18]) if partno == 6: print('CountMatchKey = 0x%x' % ((blk[18] >> 4) & 0xf)) print('CounterMatch enable = %d' % (blk[18] & 0x1)) print('ChipMode = 0x%x' % blk[19]) print() for i in which_nums: slot_conf = blk[20 + (2 * i):22 + (2 * i)] conf = SlotConfig.unpack(slot_conf) print(' Slot[%d] = 0x%s = %r' % (i, ASC(b2a_hex(slot_conf)), conf)) key_conf = blk[96 + (2 * i):2 + 96 + (2 * i)] cls = KeyConfig_508 if partno == 5 else KeyConfig_608 print('KeyConfig[%d] = 0x%s = %r' % (i, ASC(b2a_hex(key_conf)), cls.unpack(key_conf))) print() hexdump('Counter[0]: ', blk[52:60]) hexdump('Counter[1]: ', blk[60:68]) if partno == 5: hexdump('LastKeyUse: ', blk[68:84]) if partno == 6: print('UseLock = 0x%x' % blk[68]) print('VolatileKeyPermission = 0x%x' % blk[69]) hexdump('SecureBoot: ', blk[70:72]) print('KldfvLoc = 0x%x' % blk[72]) hexdump('KdflvStr: ', blk[73:75]) # 75->83 reserved print('UserExtra = 0x%x' % blk[84]) if partno == 5: print('Selector = 0x%x' % blk[85]) if partno == 6: print('UserExtraAdd = 0x%x' % blk[85]) print('LockValue = 0x%x' % blk[86]) print('LockConfig = 0x%x' % blk[87]) hexdump('SlotLocked: ', blk[88:90]) if partno == 6: hexdump('ChipOptions: ', blk[90:92]) print('ChipOptions = %r' % ChipOptions.unpack(blk[90:92])) hexdump('X509format: ', blk[92:96]) if rnd is not None: hexdump('Random: ', rnd)
def __repr__(self): return "CTxOut(nValue=%d.%08d scriptPubKey=%s)" \ % (self.nValue//1E8, self.nValue%1E8, b2a_hex(self.scriptPubKey))
def drv_entro_step2(_1, picked, _2): from glob import dis from files import CardSlot, CardMissingError the_ux.pop() index = await ux_enter_number("Index Number?", 9999) if picked in (0,1,2): # BIP-39 seed phrases (we only support English) num_words = (12, 18, 24)[picked] width = (16, 24, 32)[picked] # of bytes path = "m/83696968'/39'/0'/{num_words}'/{index}'".format(num_words=num_words, index=index) s_mode = 'words' elif picked == 3: # HDSeed for Bitcoin Core: but really a WIF of a private key, can be used anywhere s_mode = 'wif' path = "m/83696968'/2'/{index}'".format(index=index) width = 32 elif picked == 4: # New XPRV path = "m/83696968'/32'/{index}'".format(index=index) s_mode = 'xprv' width = 64 elif picked in (5, 6): width = 32 if picked == 5 else 64 path = "m/83696968'/128169'/{width}'/{index}'".format(width=width, index=index) s_mode = 'hex' else: raise ValueError(picked) dis.fullscreen("Working...") encoded = None with stash.SensitiveValues() as sv: node = sv.derive_path(path) entropy = ngu.hmac.hmac_sha512(b'bip-entropy-from-k', node.privkey()) sv.register(entropy) # truncate for this application new_secret = entropy[0:width] # only "new_secret" is interesting past here (node already blanked at this point) del node # Reveal to user! chain = chains.current_chain() if s_mode == 'words': # BIP-39 seed phrase, various lengths words = bip39.b2a_words(new_secret).split(' ') msg = 'Seed words (%d):\n' % len(words) msg += '\n'.join('%2d: %s' % (i+1, w) for i,w in enumerate(words)) encoded = stash.SecretStash.encode(seed_phrase=new_secret) elif s_mode == 'wif': # for Bitcoin Core: a 32-byte of secret exponent, base58 w/ prefix 0x80 # - always "compressed", so has suffix of 0x01 (inside base58) # - we're not checking it's on curve # - we have no way to represent this internally, since we rely on bip32 # append 0x01 to indicate it's a compressed private key pk = new_secret + b'\x01' msg = 'WIF (privkey):\n' + ngu.codecs.b58_encode(chain.b58_privkey + pk) elif s_mode == 'xprv': # Raw XPRV value. ch, pk = new_secret[0:32], new_secret[32:64] master_node = ngu.hdnode.HDNode().from_chaincode_privkey(ch, pk) encoded = stash.SecretStash.encode(xprv=master_node) msg = 'Derived XPRV:\n' + chain.serialize_private(master_node) elif s_mode == 'hex': # Random hex number for whatever purpose msg = ('Hex (%d bytes):\n' % width) + str(b2a_hex(new_secret), 'ascii') stash.blank_object(new_secret) new_secret = None # no need to print it again else: raise ValueError(s_mode) msg += '\n\nPath Used (index=%d):\n %s' % (index, path) if new_secret: msg += '\n\nRaw Entropy:\n' + str(b2a_hex(new_secret), 'ascii') #print(msg) # XXX debug prompt = '\n\nPress 1 to save to MicroSD card' if encoded is not None: prompt += ', 2 to switch to derived secret.' while 1: ch = await ux_show_story(msg+prompt, sensitive=True, escape='12') if ch == '1': # write to SD card: simple text file try: with CardSlot() as card: fname, out_fn = card.pick_filename('drv-%s-idx%d.txt' % (s_mode, index)) with open(fname, 'wt') as fp: fp.write(msg) fp.write('\n') except CardMissingError: await needs_microsd() continue except Exception as e: await ux_show_story('Failed to write!\n\n\n'+str(e)) continue await ux_show_story("Filename is:\n\n%s" % out_fn, title='Saved') else: break if new_secret is not None: stash.blank_object(new_secret) stash.blank_object(msg) if ch == '2' and (encoded is not None): from glob import dis from pincodes import pa # switch over to new secret! dis.fullscreen("Applying...") pa.tmp_secret(encoded) await ux_show_story("New master key in effect until next power down.") if encoded is not None: stash.blank_object(encoded)
def __repr__(self): return '<PinAttempt: num_fails={} delay={}/{} attempts_left={} state=0x{} hmac={}>'.format( self.num_fails, self.delay_achieved, self.delay_required, self.attempts_left, hex(self.state_flags), b2a_hex(self.hmac))
settings.set('chain', 'XTN') print("Seed phrase set, resulting XFP: " + xfp2str(settings.get('xfp'))) if '--secret' in sys.argv: # --secret 01a1a1a.... Set SE master secret directly. See SecretStash.encode from ubinascii import unhexlify as a2b_hex from ubinascii import hexlify as b2a_hex val = sys.argv[sys.argv.index('--secret') + 1] val = a2b_hex(val) assert val[0] in {0x01, 0x80, 0x81, 0x82 } or 16 <= val[0] <= 64, "bad first byte" val += bytes(72 - len(val)) SECRETS.update({ '_pin1_secret': b2a_hex(val), }) sim_defaults['terms_ok'] = 1 sim_defaults['_skip_pin'] = '12-12' sim_defaults['chain'] = 'XTN' sim_defaults['words'] = bool(val[0] & 0x80) sim_defaults.pop('xfp') sim_defaults.pop('xpub') if '-g' in sys.argv: # do login sim_defaults.pop('_skip_pin', 0) if '--nick' in sys.argv: nick = sys.argv[sys.argv.index('--nick') + 1]
def render_backup_contents(): # simple text format: # key = value # or #comments # but value is JSON from main import settings, pa rv = StringIO() def COMMENT(val=None): if val: rv.write('\n# %s\n' % val) else: rv.write('\n') def ADD(key, val): rv.write('%s = %s\n' % (key, ujson.dumps(val))) rv.write('# Coldcard backup file! DO NOT CHANGE.\n') chain = chains.current_chain() COMMENT('Private key details: ' + chain.name) with stash.SensitiveValues(bypass_pw=True) as sv: if sv.mode == 'words': ADD('mnemonic', tcc.bip39.from_data(sv.raw)) if sv.mode == 'master': ADD('bip32_master_key', b2a_hex(sv.raw)) ADD('chain', chain.ctype) ADD('xprv', chain.serialize_private(sv.node)) ADD('xpub', chain.serialize_public(sv.node)) # BTW: everything is really a duplicate of this value ADD('raw_secret', b2a_hex(sv.secret).rstrip(b'0')) if pa.has_duress_pin(): COMMENT('Duress Wallet (informational)') dpk = sv.duress_root() ADD('duress_xprv', chain.serialize_private(dpk)) ADD('duress_xpub', chain.serialize_public(dpk)) if version.has_608: # save the so-called long-secret ADD('long_secret', b2a_hex(pa.ls_fetch())) COMMENT('Firmware version (informational)') date, vers, timestamp = version.get_mpy_version()[0:3] ADD('fw_date', date) ADD('fw_version', vers) ADD('fw_timestamp', timestamp) ADD('serial', version.serial_number()) COMMENT('User preferences') # user preferences for k, v in settings.current.items(): if k[0] == '_': continue # debug stuff in simulator if k == 'xpub': continue # redundant, and wrong if bip39pw if k == 'xfp': continue # redundant, and wrong if bip39pw ADD('setting.' + k, v) if version.has_fatram: import hsm if hsm.hsm_policy_available(): ADD('hsm_policy', hsm.capture_backup()) rv.write('\n# EOF\n') return rv.getvalue()
def get_serial(self): return b2a_hex(self.data[0:4] + self.data[8:13])
def repr_params(args): return 'op=0x%x, p1=0x%x, p2=0x%x, body=%s' % (args.get( 'opcode', 0xEF), args['p1'], args['p2'], ASC(b2a_hex(args['body'])) if 'body' in args else 'None')
def write(self, b): self.fd.write(b2a_hex(b))