def generate_generic_export(account_num=0): # Generate data that other programers will use to import Coldcard (single-signer) from public_constants import AF_CLASSIC, AF_P2WPKH, AF_P2WPKH_P2SH chain = chains.current_chain() rv = dict(chain=chain.ctype, xpub = settings.get('xpub'), xfp = xfp2str(settings.get('xfp')), account = account_num, ) with stash.SensitiveValues() as sv: # each of these paths would have /{change}/{idx} in usage (not hardened) for name, deriv, fmt, atype in [ ( 'bip44', "m/44'/{ct}'/{acc}'", AF_CLASSIC, 'p2pkh' ), ( 'bip49', "m/49'/{ct}'/{acc}'", AF_P2WPKH_P2SH, 'p2sh-p2wpkh' ), # was "p2wpkh-p2sh" ( 'bip84', "m/84'/{ct}'/{acc}'", AF_P2WPKH, 'p2wpkh' ), ]: dd = deriv.format(ct=chain.b44_cointype, acc=account_num) node = sv.derive_path(dd) xfp = xfp2str(swab32(node.my_fp())) xp = chain.serialize_public(node, AF_CLASSIC) zp = chain.serialize_public(node, fmt) if fmt != AF_CLASSIC else None # bonus/check: first non-change address: 0/0 node.derive(0, False).derive(0, False) rv[name] = dict(deriv=dd, xpub=xp, xfp=xfp, first=chain.address(node, fmt), name=atype) if zp: rv[name]['_pub'] = zp return rv
def check_xpub(cls, xfp, xpub, expect_chain, xpubs, path_tops): # Shared code: consider an xpub for inclusion into a wallet, if ok, append # to list: xpubs, and path_tops try: # Note: addr fmt detected here via SLIP-132 isn't useful node, chain, _ = import_xpub(xpub) except: print(xpub) raise AssertionError('unable to parse xpub') assert node.private_key() == None, 'no private keys plz' assert chain.ctype == expect_chain, 'wrong chain, expect: ' + expect_chain # NOTE: could enforce all same depth, and/or all depth >= 1, but # seems like more restrictive than needed. if node.depth() == 1: if not xfp: # allow a shortcut: zero/omit xfp => use observed parent value xfp = swab32(node.fingerprint()) else: # generally cannot check fingerprint values, but if we can, do. assert swab32(node.fingerprint()) == xfp, 'xfp depth=1 wrong' assert xfp, 'need fingerprint' # detect, when possible, if it follows BIP45 ... find the path path_top = None if node.depth() == 1: cn = node.child_num() path_top = str(cn & 0x7fffffff) if cn & 0x80000000: path_top += "'" path_tops.add(path_top) # serialize xpub w/ BIP32 standard now. # - this has effect of stripping SLIP-132 confusion away xpubs.append((xfp, chain.serialize_public(node, AF_P2SH))) return xfp
def capture_xpub(self): # track my xpubkey fingerprint & value in settings (not sensitive really) # - we share these on any USB connection from nvstore import settings # Implicit in the values is the BIP-39 encryption passphrase, # which we might not want to actually store. xfp = swab32(self.node.my_fp()) xpub = self.chain.serialize_public(self.node) if self._bip39pw: settings.put_volatile('xfp', xfp) settings.put_volatile('xpub', xpub) else: settings.overrides.clear() settings.put('xfp', xfp) settings.put('xpub', xpub) settings.put('chain', self.chain.ctype) settings.put('words', (self.mode == 'words'))
def generate_public_contents(): # Generate public details about wallet. # # simple text format: # key = value # or #comments # but value is JSON from public_constants import AF_CLASSIC num_rx = 5 chain = chains.current_chain() with stash.SensitiveValues() as sv: xfp = xfp2str(swab32(sv.node.my_fp())) yield ('''\ # Coldcard Wallet Summary File ## For wallet with master key fingerprint: {xfp} Wallet operates on blockchain: {nb} For BIP-44, this is coin_type '{ct}', and internally we use symbol {sym} for this blockchain. ## IMPORTANT WARNING Do **not** deposit to any address in this file unless you have a working wallet system that is ready to handle the funds at that address! ## Top-level, 'master' extended public key ('m/'): {xpub} What follows are derived public keys and payment addresses, as may be needed for different systems. '''.format(nb=chain.name, xpub=chain.serialize_public(sv.node), sym=chain.ctype, ct=chain.b44_cointype, xfp=xfp)) for name, path, addr_fmt in chains.CommonDerivations: if '{coin_type}' in path: path = path.replace('{coin_type}', str(chain.b44_cointype)) if '{' in name: name = name.format(core_name=chain.core_name) show_slip132 = ('Core' not in name) yield ('''## For {name}: {path}\n\n'''.format(name=name, path=path)) yield ('''First %d receive addresses (account=0, change=0):\n\n''' % num_rx) submaster = None for i in range(num_rx): subpath = path.format(account=0, change=0, idx=i) # find the prefix of the path that is hardneded if "'" in subpath: hard_sub = subpath.rsplit("'", 1)[0] + "'" else: hard_sub = 'm' if hard_sub != submaster: # dump the xpub needed if submaster: yield "\n" node = sv.derive_path(hard_sub, register=False) yield ("%s => %s\n" % (hard_sub, chain.serialize_public(node))) if show_slip132 and addr_fmt != AF_CLASSIC and (addr_fmt in chain.slip132): yield ("%s => %s ##SLIP-132##\n" % ( hard_sub, chain.serialize_public(node, addr_fmt))) submaster = hard_sub node.blank() del node # show the payment address node = sv.derive_path(subpath, register=False) yield ('%s => %s\n' % (subpath, chain.address(node, addr_fmt))) node.blank() del node yield ('\n\n') from multisig import MultisigWallet if MultisigWallet.exists(): yield '\n# Your Multisig Wallets\n\n' from uio import StringIO for ms in MultisigWallet.get_all(): fp = StringIO() ms.render_export(fp) print("\n---\n", file=fp) yield fp.getvalue() del fp
def check_xpub(cls, xfp, xpub, deriv, expect_chain, my_xfp, xpubs): # Shared code: consider an xpub for inclusion into a wallet, if ok, append # to list: xpubs with a tuple: (xfp, deriv, xpub) # return T if it's our own key # - deriv can be None, and in very limited cases can recover derivation path # - could enforce all same depth, and/or all depth >= 1, but # seems like more restrictive than needed, so "m" is allowed try: # Note: addr fmt detected here via SLIP-132 isn't useful node, chain, _ = import_xpub(xpub) except: raise AssertionError('unable to parse xpub') assert node.private_key() == None # 'no privkeys plz' assert chain.ctype == expect_chain # 'wrong chain' depth = node.depth() if depth == 1: if not xfp: # allow a shortcut: zero/omit xfp => use observed parent value xfp = swab32(node.fingerprint()) else: # generally cannot check fingerprint values, but if we can, do so. assert swab32(node.fingerprint()) == xfp, 'xfp depth=1 wrong' assert xfp, 'need fingerprint' # happens if bare xpub given # In most cases, we cannot verify the derivation path because it's hardened # and we know none of the private keys involved. if depth == 1: # but derivation is implied at depth==1 guess = keypath_to_str([node.child_num()], skip=0) if deriv: assert guess == deriv, '%s != %s' % (guess, deriv) else: deriv = guess # reachable? doubt it assert deriv, 'empty deriv' # or force to be 'm'? # path length of derivation given needs to match xpub's depth assert deriv[0] == 'm' p_len = deriv.count('/') assert p_len == depth, 'deriv %d != %d xpub depth (xfp=%s)' % ( p_len, depth, xfp2str(xfp)) if xfp == my_xfp: # its supposed to be my key, so I should be able to generate pubkey # - might indicate collision on xfp value between co-signers, # and that's not supported with stash.SensitiveValues() as sv: chk_node = sv.derive_path(deriv) assert node.public_key() == chk_node.public_key(), \ "(m=%s)/%s wrong pubkey" % (xfp2str(xfp), deriv[2:]) # serialize xpub w/ BIP32 standard now. # - this has effect of stripping SLIP-132 confusion away xpubs.append((xfp, deriv, chain.serialize_public(node, AF_P2SH))) return (xfp == my_xfp)
from pincodes import pa from nvstore import settings from stash import SecretStash, SensitiveValues from utils import xfp2str, swab32 tn = chains.BitcoinTestnet b32_version_pub = 0x043587cf b32_version_priv = 0x04358394 node = ngu.hdnode.HDNode() v = node.deserialize(main.TPRV) assert v == b32_version_priv assert node if settings.get('xfp') == swab32(node.my_fp()): print("right xfp already") else: settings.current = sim_defaults settings.overrides.clear() settings.set('chain', 'XTN') raw = SecretStash.encode(xprv=node) pa.change(new_secret=raw) pa.new_main_secret(raw) print("New key in effect: %s" % settings.get('xpub', 'MISSING')) print("Fingerprint: %s" % xfp2str(settings.get('xfp', 0))) assert settings.get('xfp', 0) == swab32(node.my_fp())