def read(self, ll): b = self.fd.read(ll * 2) if not b: return b assert len(b) % 2 == 0 self.pos += len(b) // 2 return a2b_hex(b)
def test_b39(): import bip39 from ngu_tests import b39_vectors for raw, words, ms, xprv in b39_vectors.english: ms = a2b_hex(ms) x = HDNode() x.from_master(ms) assert x.serialize(0x0488ADE4, 1) == xprv
def encode_simple_cbor(data): buffer_data = a2b_hex(data) l = len(buffer_data) if l <= 0 or l >= 2**32: raise ValueError('length is too big') header = compose_header(l) encoded = header + buffer_data return encoded
def check_digest(digest, payload): decoded = decode_bc32_data(payload) if decoded == None: raise ValueError('Unable to decode payload: {}'.format(payload)) decoded_bytes = a2b_hex(decoded) sha = sha256_hash(decoded_bytes) decoded_digest = b2a_hex(sha).decode() # bytearray comp_digest = decode_bc32_data(digest) if comp_digest != decoded_digest: raise ValueError('Invalid digest:\n digest={}\n payload={}\nssSspayload digest={}'.format(digest, payload, decoded_digest))
def my_gate(method, buf_io, arg2): # do a command/response over unix pipe bb = b2a_hex(buf_io).decode('ascii') if buf_io is not None else 'None' msg = '%d, %s, %d\n' % (method, bb, arg2) # send to real python req.write(msg) # must use readline here for response ln = resp.readline().decode('ascii') rv, buf = ln.strip().split(',') if len(buf): buf_io[:] = a2b_hex(buf.strip()) return int(rv)
def read_flash(): print("Reading SPI flash... ", end='') from sflash import SPIFlash a = SPIFlash.array req.write('-99, None, 0\n') addr = 0xe0000 for i in range(0, 0x20000, 256): b = a2b_hex(resp.readline().strip()) assert len(b) == 256 a[addr + i:addr + i + 256] = b print("done") # robustness/error detection import sim_settings del sim_settings.sim_defaults
async def clone_write_data(*a): # Write encrypted backup file, for cloning purposes, based on a public key # found on the SD Card. # - input file must already exist on inserted card from files import CardSlot, CardMissingError try: with CardSlot() as card: path = card.get_sd_root() with open(path + '/ccbk-start.json', 'rb') as fd: d = ujson.load(fd) his_pubkey = a2b_hex(d.get('pubkey')) # expect compress pubkey assert len(his_pubkey) == 33 assert 2 <= his_pubkey[0] <= 3 # remove any other clone-files on this card, so no confusion # on receiving end; unlikely they can work anyway since new key each time for path in card.get_paths(): for fn, ftype, *var in uos.ilistdir(path): if fn.endswith('-ccbk.7z'): try: uos.remove(path + '/' + fn) except: pass except (CardMissingError, OSError) as exc: # Standard msg shown if no SD card detected when we need one. await ux_show_story( "Start this process on the other Coldcard, which will write a file onto MicroSD card as the first step.\n\nInsert that card and try again here." ) return # pick our own temp keys for this encryption pair = ngu.secp256k1.keypair() my_pubkey = pair.pubkey().to_bytes(False) session_key = pair.ecdh_multiply(his_pubkey) words = [b2a_hex(session_key).decode()] fname = b2a_hex(my_pubkey).decode() + '-ccbk.7z' await write_complete_backup(words, fname, allow_copies=False) await ux_show_story( "Done.\n\nTake this MicroSD card back to other Coldcard and continue from there." )
def decode_simple_cbor(data): data_buffer = a2b_hex(data) l = len(data_buffer) if l <= 0: raise ValueError('invalid length (<=0)') header = data_buffer[0] if header < 0x58: data_length = header - 0x40 return hexlify(data_buffer[1:1 + data_length]).decode() elif header == 0x58: data_length = int.from_bytes(data_buffer[1:2], 'big') return hexlify(data_buffer[2:2 + data_length]).decode() elif header == 0x59: data_length = int.from_bytes(data_buffer[1:3], 'big') return hexlify(data_buffer[3:3 + data_length]).decode() elif header == 0x60: data_length = int.from_bytes(data_buffer[1:5], 'big') return hexlify(data_buffer[5:5 + data_length]).decode()
def pin_stuff(submethod, buf_io): from pincodes import (PIN_ATTEMPT_SIZE, PIN_ATTEMPT_FMT, PA_ZERO_SECRET, 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: return ERANGE global SECRETS (magic, is_secondary, pin, pin_len, delay_achieved, delay_required, num_fails, attempt_target, state_flags, private_state, hmac, change_flags, old_pin, old_pin_len, new_pin, new_pin_len, secret) = ustruct.unpack_from(PIN_ATTEMPT_FMT, buf_io) # 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 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: state_flags = 0 return EPIN_AUTH_FAIL time.sleep(0.5) if ts == b'\0'*72: state_flags |= PA_ZERO_SECRET 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 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') hmac = b'69'*16 ustruct.pack_into(PIN_ATTEMPT_FMT, buf_io, 0, magic, is_secondary, pin.encode(), pin_len, delay_achieved, delay_required, num_fails, attempt_target, state_flags, private_state, hmac, change_flags, old_pin.encode(), old_pin_len, new_pin.encode(), new_pin_len, secret) return 0
def test_crc16w(): # test vectors assert crc16w(a2b_hex('0411')) == a2b_hex('3343') assert crc16w(a2b_hex('ff')) == a2b_hex('0202') assert crc16w(a2b_hex('aa')) == a2b_hex('fe01') assert crc16w(a2b_hex('ffaa')) == a2b_hex('f183') assert crc16w(a2b_hex('07ccbebab2')) == a2b_hex('8598') assert crc16w(a2b_hex('ffaa5500')) == a2b_hex('26f4')
elif '--wrap' in sys.argv: # p2wsh-p2sh case sim_defaults['multisig'] = [["CC-2-of-4", [2, 4], [[1130956047, "tpubDF2rnouQaaYrUEy2JM1YD3RFzew4onawGM4X2Re67gguTf5CbHonBRiFGe3Xjz7DK88dxBFGf2i7K1hef3PM4cFKyUjcbJXddaY9F5tJBoP"], [3503269483, "tpubDFcrvj5n7gyatVbr8dHCUfHT4CGvL8hREBjtxc4ge7HZgqNuPhFimPRtVg6fRRwfXiQthV9EBjNbwbpgV2VoQeL1ZNXoAWXxP2L9vMtRjax"], [2389277556, "tpubDExj5FnaUnPAjjgzELoSiNRkuXJG8Cm1pbdiA4Hc5vkAZHphibeVcUp6mqH5LuNVKbtLVZxVSzyja5X26Cfmx6pzRH6gXBUJAH7MiqwNyuM"], [3190206587, "tpubDFiuHYSJhNbHaGtB5skiuDLg12tRboh2uVZ6KGXxr8WVr28pLcS7F3gv8SsHFa2tm1jtx3VAuw56YfgRkdo6DXyfp51oygTKY3nJFT5jBMt"]], {"pp": "48'/1'/0'/1'", "ch": "XTN", "ft": 26}]] else: # P2SH: 2of4 using BIP39 passwords: "Me", "Myself", "and I", and (empty string) on simulator sim_defaults['multisig'] = [['MeMyself', [2, 4], [[3503269483, 'tpubD9429UXFGCTKJ9NdiNK4rC5ygqSUkginycYHccqSg5gkmyQ7PZRHNjk99M6a6Y3NY8ctEUUJvCu6iCCui8Ju3xrHRu3Ez1CKB4ZFoRZDdP9'], [2389277556, 'tpubD97nVL37v5tWyMf9ofh5rznwhh1593WMRg6FT4o6MRJkKWANtwAMHYLrcJFsFmPfYbY1TE1LLQ4KBb84LBPt1ubvFwoosvMkcWJtMwvXgSc'], [3190206587, 'tpubD9ArfXowvGHnuECKdGXVKDMfZVGdephVWg8fWGWStH3VKHzT4ph3A4ZcgXWqFu1F5xGTfxncmrnf3sLC86dup2a8Kx7z3xQ3AgeNTQeFxPa'], [1130956047, 'tpubD8NXmKsmWp3a3DXhbihAYbYLGaRNVdTnr6JoSxxfXYQcmwVtW2hv8QoDwng6JtEonmJoL3cNEwfd2cLXMpGezwZ2vL2dQ7259bueNKj9C8n']], {'ch': 'XTN', 'pp': "45'"}]] sim_defaults['fee_limit'] = -1 if '--xfp' in sys.argv: # --xfp aabbccdd => pretend we know that key (won't be able to sign) from ustruct import unpack from utils import xfp2str from ubinascii import unhexlify as a2b_hex xfp = sys.argv[sys.argv.index('--xfp') + 1] sim_defaults['xfp'] = unpack("<I", a2b_hex(xfp))[0] print("Override XFP: " + xfp2str(sim_defaults['xfp'])) if '--mainnet' in sys.argv: sim_defaults['chain'] = 'BTC' if '--seed' in sys.argv: # --seed "word1 word2 ... word24" => import that seed phrase at start from ustruct import unpack from utils import xfp2str from seed import set_seed_value from main import pa from nvstore import settings words = sys.argv[sys.argv.index('--seed') + 1].split(' ') assert len(words) in {12, 18, 24}, "Expected space-separated words: add some quotes"
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.05) 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.05) 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(0.05) elif submethod == 4: # Fetch secrets duress_pin = SECRETS.get(kk+'_duress') secret = None if pin == duress_pin: secret = a2b_hex(SECRETS.get(kk+'_duress_secret', '00'*72)) else: if change_flags & CHANGE_DURESS_SECRET: # wants the duress secret expect = SECRETS.get(kk, '') if pin == expect: secret = a2b_hex(SECRETS.get(kk+'_duress_secret', '00'*72)) else: # main/secondary secret expect = SECRETS.get(kk, '') if pin == expect: secret = a2b_hex(SECRETS.get(kk+'_secret', '00'*72)) if secret is None: 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
def str2xfp(txt): # Inverse of xfp2str import ustruct from ubinascii import unhexlify as a2b_hex return ustruct.unpack('<I', a2b_hex(txt))[0]
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 test_vectors(): pub = 'xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8' prv = 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi' m = HDNode() assert m.deserialize(prv) == V_XPRV assert m.serialize(V_XPUB, 0) == pub assert m.pubkey() == HDNode().from_master( a2b_hex('000102030405060708090a0b0c0d0e0f')).pubkey() # m/0' d = m.copy() d.derive(0, True) assert d.depth() == 1 assert d.serialize( V_XPUB, 0 ) == 'xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw' assert d.serialize( V_XPRV, 1 ) == 'xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7' # m/0'/1 d = m.copy() d.derive(0, True) d.derive(1, False) assert d.depth() == 2 assert d.serialize( V_XPUB, 0 ) == 'xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ' assert d.serialize( V_XPRV, 1 ) == 'xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs' # m/0'/1/2' d.derive(2, True) assert d.depth() == 3 assert d.serialize( V_XPUB, 0 ) == 'xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5' assert d.serialize( V_XPRV, 1 ) == 'xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM' # m/0'/1/2'/2 d.derive(2, False) assert d.depth() == 4 assert d.serialize( V_XPUB, 0 ) == 'xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV' assert d.serialize( V_XPRV, 1 ) == 'xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334' # m/0'/1/2'/2/1000000000 d.derive(1000000000, False) assert d.depth() == 5 assert d.serialize( V_XPUB, 0 ) == 'xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy' assert d.serialize( V_XPRV, 1 ) == 'xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76' # Test Vector 2 m = HDNode() m.from_master( a2b_hex( 'fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542' )) assert m.serialize( V_XPUB, 0 ) == 'xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB' assert m.serialize( V_XPRV, 1 ) == 'xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U' for sk, hard, xpub, xprv in [ (0, False, 'xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH', 'xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt' ), (2147483647, True, 'xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a', 'xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9' ), (1, False, 'xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon', 'xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef' ), (2147483646, True, 'xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL', 'xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc' ), (2, False, 'xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt', 'xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j' ), ]: m.derive(sk, hard) assert m.serialize(V_XPUB, 0) == xpub assert m.serialize(V_XPRV, 1) == xprv assert m.depth() == 5 # Test Vector 3 m = HDNode() m.from_master( a2b_hex( '4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be' )) assert m.serialize( V_XPUB, 0 ) == 'xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13' assert m.serialize( V_XPRV, 1 ) == 'xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6' m.derive(0, 1) assert m.serialize( V_XPUB, 0 ) == 'xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y' assert m.serialize( V_XPRV, 1 ) == 'xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L'
def str2xfp(txt): # Inverse of xfp2str return ustruct.unpack('<I', a2b_hex(txt))[0]
def a2b(self, x): return a2b_hex(x)
def BB(n): return a2b_hex(n.replace(' ',''))
async def restore_from_dict(vals): # Restore from a dict of values. Already JSON decoded. # Reboot on success, return string on failure from main import pa, dis, settings from pincodes import AE_SECRET_LEN #print("Restoring from: %r" % vals) # step1: the private key # - prefer raw_secret over other values # - TODO: fail back to other values try: chain = chains.get_chain(vals.get('chain', 'BTC')) assert 'raw_secret' in vals raw = bytearray(AE_SECRET_LEN) rs = vals.pop('raw_secret') if len(rs) % 2: rs += '0' x = a2b_hex(rs) raw[0:len(x)] = x # check we can decode this right (might be different firmare) opmode, bits, node = stash.SecretStash.decode(raw) assert node # verify against xprv value (if we have it) if 'xprv' in vals: check_xprv = chain.serialize_private(node) assert check_xprv == vals['xprv'], 'xprv mismatch' except Exception as e: return ('Unable to decode raw_secret and ' 'restore the seed value!\n\n\n' + str(e)) ls = None if ('long_secret' in vals) and version.has_608: try: ls = a2b_hex(vals.pop('long_secret')) except Exception as exc: sys.print_exception(exc) # but keep going. dis.fullscreen("Saving...") dis.progress_bar_show(.25) # clear (in-memory) settings and change also nvram key # - also captures xfp, xpub at this point pa.change(new_secret=raw) # force the right chain pa.new_main_secret(raw, chain) # updates xfp/xpub # NOTE: don't fail after this point... they can muddle thru w/ just right seed if ls is not None: try: pa.ls_change(ls) except Exception as exc: sys.print_exception(exc) # but keep going # restore settings from backup file for idx, k in enumerate(vals): dis.progress_bar_show(idx / len(vals)) if not k.startswith('setting.'): continue if k == 'xfp' or k == 'xpub': continue settings.set(k[8:], vals[k]) # write out settings.save() if version.has_fatram and ('hsm_policy' in vals): import hsm hsm.restore_backup(vals['hsm_policy']) await ux_show_story( 'Everything has been successfully restored. ' 'We must now reboot to install the ' 'updated settings and/or seed.', title='Success!') from machine import reset reset()
import main fname = getattr(main, 'FILENAME', '../../testing/data/2-of-2.psbt') print("Input PSBT: " + fname) is_hex = False tl = 0 with open(fname, 'rb') as orig: while 1: 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
if '-s' in sys.argv: # MicroSD menu from main import numpad numpad.inject('4') numpad.inject('y') numpad.inject('4') numpad.inject('y') if '-a' in sys.argv: # Address Explorer from main import numpad numpad.inject('4') numpad.inject('y') numpad.inject('4') numpad.inject('8') numpad.inject('y') numpad.inject('y') if '--xfp' in sys.argv: # --xfp aabbccdd => pretend we know that key (won't be able to sign) from ustruct import unpack from utils import xfp2str from ubinascii import unhexlify as a2b_hex xfp = sys.argv[sys.argv.index('--xfp') + 1] sim_defaults['xfp'] = unpack(">I", a2b_hex(xfp))[0] print("Override XFP: " + xfp2str(sim_defaults['xfp'])) # EOF
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')