Merge remote-tracking branch 'origin' into mk4

This commit is contained in:
Peter D. Gray 2021-09-16 08:27:45 -04:00
commit 689b63ea24
No known key found for this signature in database
GPG Key ID: F0E6CC6AFC16CF7B
8 changed files with 168 additions and 9 deletions

View File

@ -146,6 +146,9 @@ Type: `urn:nfc:ext:bitcoin.org:sha256`
Body: Exactly 32 bytes of binary. It's the SHA256 over the main
payload (PSBT file, for example).
If present, this value will always directly preceed the object (txn
or PSBT) that it covers. NFC-V has CRC16 over each low-level message,
but that's all.
## TXID Value
@ -184,4 +187,10 @@ When the Coldcard has signed and finalized a transaction, it can
share it in this format. Typically the user will want to broadcast
this new transaction on the Bitcoin P2P network.
# Examples
This section will include a number of examples, with analysis of the content.
- __comming soon__

View File

@ -4,6 +4,8 @@
our current XFP value and try to sign the input (same for change outputs and change-fraud
checks). This makes building a workable PSBT file easier and could be used to preserve
privacy of XFP value itself. A warning is shown when this happens.
- Enhancement: "Advanced > Export XPUB" provides direct way to show XPUB (or ZPUB/YPUB) for
BIP-84 / BIP-44 / BIP-49 standard derivations, as a QR. Also can show XFP and master XPUB.
## 4.1.3 - Sep 2, 2021

View File

@ -949,6 +949,68 @@ that you will need to import other wallet software to track balance.''' + SENSIT
import export
await export.make_summary_file()
async def export_xpub(label, _2, item):
# provide bare xpub in a QR/NFC for import into simple wallets.
import chains, glob, stash
from public_constants import AF_CLASSIC
from ux import show_qr_code
chain = chains.current_chain()
acct = 0
# decode menu code => standard derivation
mode = item.arg
if mode == -1:
# XFP shortcut
xfp = xfp2str(settings.get('xfp', 0))
await show_qr_code(xfp, True)
return
elif mode == 0:
path = "m"
addr_fmt = AF_CLASSIC
else:
remap = {44:0, 49:1, 84:2}[mode]
_, path, addr_fmt = chains.CommonDerivations[remap]
path = path.format(account='{acct}', coin_type=chain.b44_cointype, change=0, idx=0)[:-4]
# always show SLIP-132 style, because defacto
show_slip132 = (addr_fmt != AF_CLASSIC)
while 1:
msg = '''Show QR of the XPUB for path:\n\n%s\n\n''' % path
if '{acct}' in path:
msg += "Press 1 to select account other than zero. "
#if glob.NFC:
# msg = "Press 3 to share over NFC. "
ch = await ux_show_story(msg, escape='13')
if ch == 'x': return
if ch == '1':
acct = await ux_enter_number('Account Number:', 9999) or 0
path = path.format(acct=acct)
continue
# assume zero account if not picked
path = path.format(acct=acct)
# render xpub/ypub/zpub
with stash.SensitiveValues() as sv:
print(path)
node = sv.derive_path(path) if path != 'm' else sv.node
xpub = chain.serialize_public(node, addr_fmt)
#if ch == '3' and glob.NFC:
# await glob.NFC.share_text(xpub)
#else:
if 1:
from ux import show_qr_code
await show_qr_code(xpub, False)
break
def electrum_export_story(background=False):
# saves memory being in a function
return ('''\
@ -1514,6 +1576,7 @@ to consume all but the final PIN attempt.\
MenuItem('Brick Mode', chooser=set_countdown_pin_mode),
]
async def pin_changer(_1, _2, item):
# Help them to change pins with appropriate warnings.
# - forcing them to drill-down to get warning about secondary is on purpose

View File

@ -299,7 +299,6 @@ def slip32_deserialize(xp):
# - single signer only
CommonDerivations = [
# name, path.format(), addr format
#Obsolete, removed: ( 'Electrum (not BIP-44)', "m/{change}/{idx}", AF_CLASSIC ),
( 'BIP-44 / Electrum', "m/44'/{coin_type}'/{account}'/{change}/{idx}", AF_CLASSIC ),
( 'BIP-49 (P2WPKH-nested-in-P2SH)', "m/49'/{coin_type}'/{account}'/{change}/{idx}",
AF_P2WPKH_P2SH ), # generates 3xxx/2xxx p2sh-looking addresses

View File

@ -91,6 +91,15 @@ SettingsMenu = [
MenuItem('Display Units', chooser=value_resolution_chooser),
]
XpubExportMenu = [
# xxxxxxxxxxxxxxxx
MenuItem("Segwit (BIP-84)", f=export_xpub, arg=84),
MenuItem("Classic (BIP-44)", f=export_xpub, arg=44),
MenuItem("P2WPKH/P2SH (49)", f=export_xpub, arg=49),
MenuItem("Master XPUB", f=export_xpub, arg=0),
MenuItem("Current XFP", f=export_xpub, arg=-1),
]
WalletExportMenu = [
# xxxxxxxxxxxxxxxx (alphabetical ordering)
MenuItem("Bitcoin Core", f=bitcoin_core_skeleton),
@ -98,6 +107,7 @@ WalletExportMenu = [
MenuItem("Wasabi Wallet", f=wasabi_skeleton),
MenuItem("Unchained Capital", f=unchained_capital_export),
MenuItem("Generic JSON", f=generic_skeleton),
MenuItem("Export XPUB", menu=XpubExportMenu),
]
SDCardMenu = [
@ -204,6 +214,7 @@ AdvancedNormalMenu = [
MenuItem('Paper Wallets', f=make_paper_wallet, predicate=lambda: make_paper_wallet),
MenuItem('User Management', menu=make_users_menu, predicate=lambda: version.has_fatram),
MenuItem('Derive Entropy', f=drv_entro_start),
MenuItem("Export XPUB", menu=XpubExportMenu),
MenuItem("Danger Zone", menu=DangerZoneMenu),
]

View File

@ -24,7 +24,7 @@ if ! touch repro-build.sh ; then
cd firmware/external
git submodule update --init
cd ../stm32
rsync -av /work/src/releases/*.dfu ../releases
rsync --ignore-missing-args -av /work/src/releases/*.dfu ../releases
fi
# need signit.py in path
@ -34,7 +34,6 @@ python -m pip install --editable .
cd ../stm32
cd ../releases
ls *.dfu
if [ -f *-v$VERSION_STRING-coldcard.dfu ]; then
echo "Using existing binary in ../releases, not downloading."
else

View File

@ -150,6 +150,18 @@ def need_keypress(dev, request):
raise pytest.fail('need to provide keypresses')
return doit
@pytest.fixture(scope='module')
def enter_number(need_keypress):
def doit(number):
number = str(number) if not isinstance(number, str) else number
for d in number:
need_keypress(d)
need_keypress('y')
return doit
@pytest.fixture(scope='module')
def master_xpub(dev):

View File

@ -15,7 +15,7 @@ from ckcc_protocol.constants import AF_CLASSIC, AF_P2WPKH, AF_P2WSH_P2SH
from pprint import pprint
@pytest.mark.parametrize('acct_num', [ None, '0', '99', '123'])
def test_export_core(dev, acct_num, cap_menu, pick_menu_item, goto_home, cap_story, need_keypress, microsd_path, bitcoind_wallet, bitcoind_d_wallet):
def test_export_core(dev, acct_num, cap_menu, pick_menu_item, goto_home, cap_story, need_keypress, microsd_path, bitcoind_wallet, bitcoind_d_wallet, enter_number):
# test UX and operation of the 'bitcoin core' wallet export
from pycoin.contrib.segwit_addr import encode as sw_encode
@ -35,12 +35,10 @@ def test_export_core(dev, acct_num, cap_menu, pick_menu_item, goto_home, cap_sto
if acct_num is not None:
need_keypress('1')
time.sleep(0.1)
for n in acct_num:
need_keypress(n)
enter_number(acct_num)
else:
acct_num = '0'
need_keypress('y')
need_keypress('y')
time.sleep(0.1)
title, story = cap_story()
@ -198,7 +196,7 @@ def test_export_wasabi(dev, cap_menu, pick_menu_item, goto_home, cap_story, need
@pytest.mark.parametrize('mode', [ "Legacy (P2PKH)", "P2SH-Segwit", "Native Segwit"])
@pytest.mark.parametrize('acct_num', [ None, '0', '99', '123'])
def test_export_electrum(mode, acct_num, dev, cap_menu, pick_menu_item, goto_home, cap_story, need_keypress, microsd_path):
def test_export_electrum(mode, acct_num, dev, cap_menu, pick_menu_item, goto_home, cap_story, need_keypress, microsd_path, use_mainnet):
# lightly test electrum wallet export
goto_home()
@ -447,4 +445,70 @@ def test_export_public_txt(dev, cap_menu, pick_menu_item, goto_home, cap_story,
addr_vs_path(rhs, path=lhs, addr_fmt=f)
@pytest.mark.qrcode
@pytest.mark.parametrize('acct_num', [ None, 0, 99, 8989])
def test_export_xpub(dev, acct_num, cap_menu, pick_menu_item, goto_home, cap_story, need_keypress, enter_number, cap_screen_qr, use_mainnet):
# XPUB's via QR
use_mainnet()
goto_home()
pick_menu_item('Advanced')
pick_menu_item('Export XPUB')
top_items = cap_menu()
for m in top_items:
is_xfp = False
if '-84' in m:
expect = "m/84'/0'/{acct}'"
elif '-44' in m:
expect = "m/44'/0'/{acct}'"
elif '49' in m:
expect = "m/49'/0'/{acct}'"
elif 'Master' in m:
expect = "m"
elif 'XFP' in m:
is_xfp = True
pick_menu_item(m)
time.sleep(0.1)
if is_xfp:
got = cap_screen_qr().decode('ascii')
assert got == xfp2str(simulator_fixed_xfp).upper()
need_keypress('x')
continue
title, story = cap_story()
assert expect in story
if 'acct' in expect:
assert "Press 1 to select account" in story
if acct_num is not None:
need_keypress('1')
enter_number(acct_num)
time.sleep(0.1)
expect = expect.format(acct=acct_num)
title, story = cap_story()
assert expect in story
assert "Press 1 to select account" not in story
expect = expect.format(acct=0)
need_keypress('y')
got_pub = cap_screen_qr().decode('ascii')
if got_pub[0] not in 'xt':
got_pub,*_ = slip132undo(got_pub)
got = BIP32Node.from_wallet_key(got_pub)
wallet = BIP32Node.from_wallet_key(simulator_fixed_xprv)
if expect != 'm':
wallet = wallet.subkey_for_path(expect[2:])
assert got.sec() == wallet.sec()
need_keypress('x')
# EOF