def generate_bitcoin_core_wallet(example_addrs, account_num): # Generate the data for an RPC command to import keys into Bitcoin Core # - yields dicts for json purposes from descriptor import append_checksum from main import settings import ustruct from public_constants import AF_P2WPKH chain = chains.current_chain() derive = "84'/{coin_type}'/{account}'".format(account=account_num, coin_type=chain.b44_cointype) with stash.SensitiveValues() as sv: prefix = sv.derive_path(derive) xpub = chain.serialize_public(prefix) for i in range(3): sp = '0/%d' % i node = sv.derive_path(sp, master=prefix) a = chain.address(node, AF_P2WPKH) example_addrs.append(('m/%s/%s' % (derive, sp), a)) xfp = settings.get('xfp') txt_xfp = xfp2str(xfp).lower() chain = chains.current_chain() _, vers, _ = version.get_mpy_version() for internal in [False, True]: desc = "wpkh([{fingerprint}/{derive}]{xpub}/{change}/*)".format( derive=derive.replace("'", "h"), fingerprint=txt_xfp, coin_type=chain.b44_cointype, account=0, xpub=xpub, change=(1 if internal else 0)) yield { 'desc': append_checksum(desc), 'range': [0, 1000], 'timestamp': 'now', 'internal': internal, 'keypool': True, 'watchonly': True }
async def electrum_skeleton(*A): # save xpub, and some other public details into a file import chains ch = chains.current_chain() if not await ux_show_story('''\ This saves a skeleton Electrum wallet file onto the MicroSD card. \ You can then open that file in Electrum without ever connecting this Coldcard to a computer.\n Choose an address type for the wallet on the next screen. ''' + SENSITIVE_NOT_SECRET): return # pick segwit or classic derivation+such from public_constants import AF_CLASSIC, AF_P2WPKH, AF_P2WPKH_P2SH from menu import MenuSystem, MenuItem # Ordering and terminology from similar screen in Electrum. I prefer # 'classic' instead of 'legacy' personallly. rv = [] if AF_CLASSIC in ch.slip132: rv.append(MenuItem("Legacy (P2PKH)", f=electrum_skeleton_step2, arg=AF_CLASSIC)) if AF_P2WPKH_P2SH in ch.slip132: rv.append(MenuItem("P2SH-Segwit", f=electrum_skeleton_step2, arg=AF_P2WPKH_P2SH)) if AF_P2WPKH in ch.slip132: rv.append(MenuItem("Native Segwit", f=electrum_skeleton_step2, arg=AF_P2WPKH)) return MenuSystem(rv)
def make_msg(start): msg = '' if start == 0: msg = "Press 1 to save to MicroSD." if version.has_fatram: msg += " 4 to view QR Codes." msg += '\n\n' msg += "Addresses %d..%d:\n\n" % (start, start + n - 1) addrs = [] chain = chains.current_chain() dis.fullscreen('Wait...') with stash.SensitiveValues() as sv: for idx in range(start, start + n): subpath = path.format(account=0, change=0, idx=idx) node = sv.derive_path(subpath, register=False) addr = chain.address(node, addr_fmt) addrs.append(addr) msg += "%s =>\n%s\n\n" % (subpath, addr) dis.progress_bar_show(idx / n) stash.blank_object(node) msg += "Press 9 to see next group, 7 to go back. X to quit." return msg, addrs
def make_msg(start): msg = '' if start == 0: msg = "Press 1 to save to MicroSD." msg += '\n\n' msg += "Addresses %d..%d:\n\n" % (start, start + n - 1) addrs = [] chain = chains.current_chain() dis.fullscreen('Loading...') with stash.SensitiveValues() as sv: for idx in range(start, start + n): subpath = path.format(account=0, change=0, idx=idx) node = sv.derive_path(subpath, register=False) addr = chain.address(node, addr_fmt) addr1 = addr[:16] addr2 = addr[16:] addrs.append(addr) msg += "%s =>\n %s\n %s\n\n" % (subpath, addr1, addr2) dis.progress_bar_show(idx / n) stash.blank_object(node) msg += "Press 9 to see next group.\nPress 7 to see prev. group." return msg, addrs
def generate_public_contents(): # Generate public details about wallet. # # simple text format: # key = value # or #comments # but value is JSON from main import settings num_rx = 5 chain = chains.current_chain() with stash.SensitiveValues() as sv: yield ('''\ # Coldcard Wallet Summary File ## Wallet operates on blockchain: {nb} For BIP44, this is coin_type '{ct}', and internally we use symbol {sym} for this blockchain. ## Top-level, 'master' extended public key ('m/'): {xpub} Derived public keys, as may be needed for different systems: '''.format(nb=chain.name, xpub=chain.serialize_public(sv.node), sym=chain.ctype, ct=chain.b44_cointype)) for name, path, addr_fmt in chains.CommonDerivations: if '{coin_type}' in path: path = path.replace('{coin_type}', str(chain.b44_cointype)) yield ('''## For {name}: {path}\n\n'''.format(name=name, path=path)) submaster, kids = path.split('/{', 1) kids = '{' + kids node = sv.derive_path(submaster) yield ("%s => %s\n" % (submaster, chain.serialize_public(node))) yield ( '''\n... first %d receive addresses (account=0, change=0):\n\n''' % num_rx) for i in range(num_rx): subpath = kids.format(account=0, change=0, idx=i) kid = sv.derive_path(subpath, node) yield ('%s/%s => %s\n' % (submaster, subpath, chain.address(kid, addr_fmt))) yield ('\n\n')
def generate_wasabi_wallet(): # Generate the data for a JSON file which Wasabi can open directly as a new wallet. from common import settings import ustruct import version # bitcoin (xpub) is used, even for testnet case (ie. no tpub) # - altho, doesn't matter; the wallet operates based on it's own settings for test/mainnet # regardless of the contents of the wallet file btc = chains.BitcoinMain with stash.SensitiveValues() as sv: xpub = btc.serialize_public(sv.derive_path("84'/0'/0'")) xfp = settings.get('xfp') txt_xfp = xfp2str(xfp) chain = chains.current_chain() assert chain.ctype in {'BTC', 'TBTC'}, "Only Bitcoin supported" _, vers, _ = version.get_mpy_version() return dict(MasterFingerprint=txt_xfp, ColdCardFirmwareVersion=vers, ExtPubKey=xpub)
async def interact(self): # Prompt user w/ details and get approval from main import dis, hsm_active if hsm_active: ch = await hsm_active.approve_msg_sign(self.text, self.address, self.subpath) else: story = MSG_SIG_TEMPLATE.format(msg=self.text, addr=self.address, subpath=self.subpath) ch = await ux_show_story(story) if ch != 'y': # they don't want to! self.refused = True else: # perform signing (progress bar shown) digest = chains.current_chain().hash_message(self.text.encode()) self.result = sign_message_digest(digest, self.subpath, "Signing...") if self.approved_cb: # for micro sd case await self.approved_cb(self.result, self.address) if self.approved_cb: # don't kill menu depth for file case UserAuthorizedAction.cleanup() self.pop_menu() else: self.done()
def generate_address_csv(path, addr_fmt, ms_wallet, account_num, n, start=0): # Produce CSV file contents as a generator if ms_wallet: from ubinascii import hexlify as b2a_hex # For multisig, include redeem script and derivation for each signer yield '"' + '","'.join(['Index', 'Payment Address', 'Redeem Script (%d of %d)' % (ms_wallet.M, ms_wallet.N)] + (['Derivation'] * ms_wallet.N)) + '"\n' for (idx, derivs, addr, script) in ms_wallet.yield_addresses(start, n): ln = '%d,"%s","%s","' % (idx, addr, b2a_hex(script).decode()) ln += '","'.join(derivs) ln += '"\n' yield ln return yield '"Index","Payment Address","Derivation"\n' ch = chains.current_chain() with stash.SensitiveValues() as sv: for idx in range(start, start+n): deriv = path.format(account=account_num, change=0, idx=idx) node = sv.derive_path(deriv, register=False) yield '%d,"%s","%s"\n' % (idx, ch.address(node, addr_fmt), deriv) stash.blank_object(node)
async def electrum_skeleton(*a): # save xpub, and some other public details into a file: NOT MULTISIG if not await ux_show_story(electrum_export_story()): return import chains ch = chains.current_chain() # pick segwit or classic derivation+such from public_constants import AF_CLASSIC, AF_P2WPKH, AF_P2WPKH_P2SH from menu import MenuSystem, MenuItem # Ordering and terminology from similar screen in Electrum. I prefer # 'classic' instead of 'legacy' personallly. rv = [] if AF_CLASSIC in ch.slip132: rv.append( MenuItem("Legacy (P2PKH)", f=electrum_skeleton_step2, arg=AF_CLASSIC)) if AF_P2WPKH_P2SH in ch.slip132: rv.append( MenuItem("P2SH-Segwit", f=electrum_skeleton_step2, arg=AF_P2WPKH_P2SH)) if AF_P2WPKH in ch.slip132: rv.append( MenuItem("Native Segwit", f=electrum_skeleton_step2, arg=AF_P2WPKH)) return MenuSystem(rv)
async def view_seed_words(*a): import stash, tcc if not await ux_confirm( '''The next screen will show the seed words (and if defined, your BIP39 passphrase).\n\nAnyone with knowledge of those words can control all funds in this wallet.''' ): return with stash.SensitiveValues() as sv: if sv.mode == 'words': words = tcc.bip39.from_data(sv.raw).split(' ') msg = 'Seed words (%d):\n' % len(words) msg += '\n'.join('%2d: %s' % (i + 1, w) for i, w in enumerate(words)) pw = stash.bip39_passphrase if pw: msg += '\n\nBIP39 Passphrase:\n%s' % stash.bip39_passphrase elif sv.mode == 'xprv': import chains msg = chains.current_chain().serialize_private(sv.node) elif sv.mode == 'master': from ubinascii import hexlify as b2a_hex msg = '%d bytes:\n\n' % len(sv.raw) msg += str(b2a_hex(sv.raw), 'ascii') else: raise ValueError(sv.mode) await ux_show_story(msg, sensitive=True) stash.blank_object(msg)
def generate_electrum_wallet(is_segwit): # Generate line-by-line JSON details about wallet. # # Much reverse enginerring of Electrum here. It's a complex # legacy file format. from main import settings chain = chains.current_chain() xfp = settings.get('xfp') if is_segwit: derive = "m/84'/{coin_type}'/{account}'".format(account=0, coin_type=chain.b44_cointype) else: derive = "m/44'/{coin_type}'/{account}'".format(account=0, coin_type=chain.b44_cointype) with stash.SensitiveValues() as sv: top = chain.serialize_public(sv.derive_path(derive)) # most values are nicely defaulted, and for max forward compat, don't want to set # anything more than I need to rv = dict(seed_version=17, use_encryption=False, wallet_type='standard') # the important stuff. rv['keystore'] = dict( ckcc_xfp=xfp, ckcc_xpub=settings.get('xpub'), hw_type='coldcard', label='Coldcard Import 0x%08x' % xfp, type='hardware', derivation=derive, xpub=top) return rv
def generate_unchained_export(acct_num=0): # They used to rely on our airgapped export file, so this is same style # - for multisig purposes # - BIP-45 style paths for now # - no account numbers (at this level) from public_constants import AF_P2SH, AF_P2WSH_P2SH, AF_P2WSH chain = chains.current_chain() todo = [ ("m/45'", 'p2sh', AF_P2SH), # iff acct_num == 0 ("m/48'/{coin}'/{acct_num}'/1'", 'p2sh_p2wsh', AF_P2WSH_P2SH), ("m/48'/{coin}'/{acct_num}'/2'", 'p2wsh', AF_P2WSH), ] xfp = xfp2str(settings.get('xfp', 0)) rv = dict(account=acct_num, xfp=xfp) with stash.SensitiveValues() as sv: for deriv, name, fmt in todo: if fmt == AF_P2SH and acct_num: continue dd = deriv.format(coin=chain.b44_cointype, acct_num=acct_num) node = sv.derive_path(dd) xp = chain.serialize_public(node, fmt) rv['%s_deriv' % name] = dd rv[name] = xp return rv
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 __init__(self, psbt_len, do_finalize=False, approved_cb=None): super().__init__() self.psbt_len = psbt_len self.do_finalize = do_finalize self.psbt = None self.approved_cb = approved_cb self.result = None # will be (len, sha256) of the resulting PSBT self.chain = chains.current_chain()
def make_msg(): msg = '' if n > 1: if start == 0: msg = "Press 1 to save to MicroSD." if version.has_fatram and not ms_wallet: msg += " 4 to view QR Codes." msg += '\n\n' msg += "Addresses %d..%d:\n\n" % (start, start + n - 1) else: # single address, from deep path given by user msg += "Showing single address." if version.has_fatram: msg += " Press 4 to view QR Codes." msg += '\n\n' addrs = [] chain = chains.current_chain() dis.fullscreen('Wait...') if ms_wallet: # IMPORTANT safety feature: never show complete address # but show enough they can verify addrs shown elsewhere. # - makes a redeem script # - converts into addr # - assumes 0/0 is first address. for (i, paths, addr, script) in ms_wallet.yield_addresses(start, n): if i == 0 and ms_wallet.N <= 4: msg += '\n'.join(paths) + '\n =>\n' else: msg += '.../0/%d =>\n' % i addrs.append(addr) msg += truncate_address(addr) + '\n\n' dis.progress_bar_show(i/n) else: # single-singer wallets with stash.SensitiveValues() as sv: for idx in range(start, start + n): deriv = path.format(account=self.account_num, change=0, idx=idx) node = sv.derive_path(deriv, register=False) addr = chain.address(node, addr_fmt) addrs.append(addr) msg += "%s =>\n%s\n\n" % (deriv, addr) dis.progress_bar_show(idx/n) stash.blank_object(node) if n > 1: msg += "Press 9 to see next group, 7 to go back. X to quit." return msg, addrs
async def make_multisig_menu(*a): # list of all multisig wallets, and high-level settings/actions if chains.current_chain().ctype != 'XTN': if not await ux_confirm( "Multisig is to be used on Testnet coins only at this time."): return rv = MultisigMenu.construct() return MultisigMenu(rv)
async def choose_first_address(*a): # Choose from a truncated list of index 0 common addresses, remember # the last address the user selected and use it as the default from main import settings, dis chain = chains.current_chain() dis.fullscreen('Wait...') with stash.SensitiveValues() as sv: def truncate_address(addr): # Truncates address to width of screen, replacing middle chars middle = "-" leftover = SCREEN_CHAR_WIDTH - len(middle) start = addr[0:(leftover + 1) // 2] end = addr[len(addr) - (leftover // 2):] return start + middle + end # Create list of choices (address_index_0, path, addr_fmt) choices = [] for name, path, addr_fmt in chains.CommonDerivations: if '{coin_type}' in path: path = path.replace('{coin_type}', str(chain.b44_cointype)) subpath = path.format(account=0, change=0, idx=0) node = sv.derive_path(subpath, register=False) address = chain.address(node, addr_fmt) choices.append((truncate_address(address), path, addr_fmt)) dis.progress_bar_show(len(choices) / len(chains.CommonDerivations)) stash.blank_object(node) picked = None async def clicked(_1, _2, item): if picked is None: picked = item.arg the_ux.pop() items = [ MenuItem(address, f=clicked, arg=i) for i, (address, path, addr_fmt) in enumerate(choices) ] menu = MenuSystem(items) menu.goto_idx(settings.get('axi', 0)) the_ux.push(menu) await menu.interact() if picked is None: return None # update last clicked address settings.put('axi', picked) address, path, addr_fmt = choices[picked] return (path, addr_fmt)
def __enter__(self): import chains self.mode, self.raw, self.node = SecretStash.decode(self.secret) self.chain = chains.current_chain() self.spots = [self.secret, self.node, self.raw] return self
def __enter__(self): import chains self.mode, self.raw, self.node = SecretStash.decode(self.secret, self._bip39pw) self.spots.append(self.node) self.spots.append(self.raw) self.chain = chains.current_chain() return self
def import_from_psbt(cls, M, N, xpubs_list): # given the raw data fro PSBT global header, offer the user # the details, and/or bypass that all and just trust the data. # - xpubs_list is a list of (xfp+path, binary BIP32 xpub) # - already know not in our records. from ustruct import unpack_from from main import settings import tcc trust_mode = cls.get_trust_policy() if trust_mode == TRUST_VERIFY: # already checked for existing import and wasn't found, so fail raise AssertionError( "XPUBs in PSBT do not match any existing wallet") # build up an in-memory version of the wallet. assert N == len(xpubs_list) assert 1 <= M <= N <= MAX_SIGNERS, 'M/N range' my_xfp = settings.get('xfp') expect_chain = chains.current_chain().ctype xpubs = [] has_mine = False path_tops = set() for k, v in xpubs_list: xfp, *path = unpack_from('<%dI' % (len(k) / 4), k, 0) xpub = tcc.codecs.b58_encode(v) xfp = cls.check_xpub(xfp, xpub, expect_chain, xpubs, path_tops) if xfp == my_xfp: has_mine = True assert has_mine, 'my key not included' name = 'PSBT-%d-of-%d' % (M, N) prefix = path_tops.pop() if len(path_tops) == 1 else None ms = cls(name, (M, N), xpubs, chain_type=expect_chain, common_prefix=prefix) if trust_mode == TRUST_PSBT: # keep just in-memory version, no approval required return ms, False assert trust_mode == TRUST_OFFER # caller need to handle interact w.r.t new wallet print("Offering import") return ms, True
async def make_bitcoin_core_wallet(account_num=0, fname_pattern='bitcoin-core.txt'): from glob import dis import ustruct xfp = xfp2str(settings.get('xfp')) dis.fullscreen('Generating...') # make the data examples = [] imp_multi = [] imp_desc = [] for a, b in generate_bitcoin_core_wallet(account_num, examples): imp_multi.append(a) imp_desc.append(b) imp_multi = ujson.dumps(imp_multi) imp_desc = ujson.dumps(imp_desc) body = '''\ # Bitcoin Core Wallet Import File https://github.com/Coldcard/firmware/blob/master/docs/bitcoin-core-usage.md ## For wallet with master key fingerprint: {xfp} Wallet operates on blockchain: {nb} ## Bitcoin Core RPC The following command can be entered after opening Window -> Console in Bitcoin Core, or using bitcoin-cli: importdescriptors '{imp_desc}' ### Bitcoin Core before v0.21.0 This command can be used on older versions, but it is not as robust and "importdescriptors" should be prefered if possible: importmulti '{imp_multi}' ## Resulting Addresses (first 3) '''.format(imp_multi=imp_multi, imp_desc=imp_desc, xfp=xfp, nb=chains.current_chain().name) body += '\n'.join('%s => %s' % t for t in examples) body += '\n' await write_text_file(fname_pattern, body, 'Bitcoin Core')
def __init__(self, psbt_len, flags=0x0, approved_cb=None, psbt_sha=None): super().__init__() self.psbt_len = psbt_len self.do_finalize = bool(flags & STXN_FINALIZE) self.do_visualize = bool(flags & STXN_VISUALIZE) self.stxn_flags = flags self.psbt = None self.psbt_sha = psbt_sha self.approved_cb = approved_cb self.result = None # will be (len, sha256) of the resulting PSBT self.chain = chains.current_chain()
def generate_address_csv(path, addr_fmt, n): # Produce CSV file contents as a generator yield '"Index","Payment Address","Derivation"\n' ch = chains.current_chain() with stash.SensitiveValues() as sv: for idx in range(n): subpath = path.format(account=0, change=0, idx=idx) node = sv.derive_path(subpath, register=False) yield '%d,"%s","%s"\n' % (idx, ch.address(node, addr_fmt), subpath) stash.blank_object(node)
async def bitcoin_core_skeleton(*A): # save output descriptors into a file # - user has no choice, it's going to be bech32 with m/84'/{coin_type}'/0' path import chains ch = chains.current_chain() if await ux_show_story('''\ This saves a command onto the MicroSD card that includes the public keys.\ You can then run that command in Bitcoin Core without ever connecting this Coldcard to a computer.\ ''' + SENSITIVE_NOT_SECRET) != 'y': return # no choices to be made, just do it. with imported('backups') as bk: await bk.make_bitcoin_core_wallet()
async def wasabi_skeleton(*A): # save xpub, and some other public details into a file # - user has no choice, it's going to be bech32 with m/84'/0'/0' path import chains ch = chains.current_chain() if await ux_show_story('''\ This saves a skeleton Wasabi wallet file onto the MicroSD card. \ You can then open that file in Wasabi without ever connecting this Coldcard to a computer.\ ''' + SENSITIVE_NOT_SECRET) != 'y': return # no choices to be made, just do it. with imported('backups') as bk: await bk.make_json_wallet('Wasabi wallet', lambda: bk.generate_wasabi_wallet(), 'new-wasabi.json')
async def render(self): # Choose from a truncated list of index 0 common addresses, remember # the last address the user selected and use it as the default from glob import dis chain = chains.current_chain() dis.fullscreen('Wait...') with stash.SensitiveValues() as sv: # Create list of choices (address_index_0, path, addr_fmt) choices = [] for name, path, addr_fmt in chains.CommonDerivations: if '{coin_type}' in path: path = path.replace('{coin_type}', str(chain.b44_cointype)) if self.account_num != 0 and '{account}' not in path: # skip derivations that are not affected by account number continue deriv = path.format(account=self.account_num, change=0, idx=0) node = sv.derive_path(deriv, register=False) address = chain.address(node, addr_fmt) choices.append( (truncate_address(address), path, addr_fmt) ) dis.progress_bar_show(len(choices) / len(chains.CommonDerivations)) stash.blank_object(node) items = [MenuItem(address, f=self.pick_single, arg=(path, addr_fmt)) for i, (address, path, addr_fmt) in enumerate(choices)] # some other choices if self.account_num == 0: items.append(MenuItem("Account Number", f=self.change_account)) items.append(MenuItem("Custom Path", menu=self.make_custom)) # if they have MS wallets, add those next for ms in MultisigWallet.iter_wallets(): if not ms.addr_fmt: continue items.append(MenuItem(ms.name, f=self.pick_multisig, arg=ms)) else: items.append(MenuItem("Account: %d" % self.account_num, f=self.change_account)) self.goto_idx(settings.get('axi', 0)) # weak self.replace_items(items)
def generate_electrum_wallet(addr_type, account_num=0): # Generate line-by-line JSON details about wallet. # # Much reverse enginerring of Electrum here. It's a complex # legacy file format. from main import settings from public_constants import AF_CLASSIC, AF_P2WPKH, AF_P2WPKH_P2SH chain = chains.current_chain() xfp = settings.get('xfp') # Must get the derivation path, and the SLIP32 version bytes right! if addr_type == AF_CLASSIC: mode = 44 elif addr_type == AF_P2WPKH: mode = 84 elif addr_type == AF_P2WPKH_P2SH: mode = 49 else: raise ValueError(addr_type) derive = "m/{mode}'/{coin_type}'/{account}'".format( mode=mode, account=account_num, coin_type=chain.b44_cointype) with stash.SensitiveValues() as sv: top = chain.serialize_public(sv.derive_path(derive), addr_type) # most values are nicely defaulted, and for max forward compat, don't want to set # anything more than I need to rv = dict(seed_version=17, use_encryption=False, wallet_type='standard') lab = 'Coldcard Import %s' % xfp2str(xfp) if account_num: lab += ' Acct#%d' % account_num # the important stuff. rv['keystore'] = dict(ckcc_xfp=xfp, ckcc_xpub=settings.get('xpub'), hw_type='coldcard', type='hardware', label=lab, derivation=derive, xpub=top) return rv
def verify_amount(cls, prevout, amount, in_idx): # check this input either: # - not been seen before, in which case, record it # - OR: the amount matches exactly, any previously-seend UTXO w/ same outpoint # raises IncorrectUTXOAmount with details if it fails, which should abort any signing exp = cls.fetch_amount(prevout) if exp is None: # new entry, add it cls.add(prevout, amount) elif exp != amount: # Found the hacking we are looking for! ch = chains.current_chain() exp, units = ch.render_value(exp, True) amount, _ = ch.render_value(amount, True) raise IncorrectUTXOAmount(in_idx, "Expected %s but PSBT claims %s %s" % ( exp, amount, units))
def handle_xpub(self, subpath): # Share the xpub for the indicated subpath. Expects # a text string which is the path derivation. # TODO: might not have a privkey yet from chains import current_chain from utils import cleanup_deriv_path subpath = cleanup_deriv_path(subpath) chain = current_chain() with stash.SensitiveValues() as sv: node = sv.derive_path(subpath) xpub = chain.serialize_public(node) return b'asci' + xpub.encode()
def to_text(self): # Text for humans to read and approve. chain = chains.current_chain() def render(n): return ' '.join(chain.render_value(n, True)) if self.per_period is not None: rv = 'Up to %s per period' % render(self.per_period) if self.max_amount is not None: rv += ', and up to %s per txn' % render(self.max_amount) elif self.max_amount is not None: rv = 'Up to %s per txn' % render(self.max_amount) else: rv = 'Any amount' if self.wallet == '1': rv += ' (non multisig)' elif self.wallet: rv += ' from multisig wallet "%s"' % self.wallet if self.users: rv += ' may be authorized by ' if self.min_users == len(self.users) == 1: rv += 'user: '******'all users: ' + ', '.join(self.users) elif self.min_users == 1: rv += 'any one user: '******' OR '.join(self.users) elif self.min_users: rv += 'at least %d users: ' % self.min_users rv += ', '.join(self.users) else: rv += ' will be approved' if self.whitelist: rv += ' provided it goes to: ' + ', '.join(self.whitelist) if self.local_conf: rv += ' if local user confirms' return rv