restore backup via USB; new CLI commnad "restore"; add backup restore to "upload" cmd

This commit is contained in:
scgbckbone 2025-08-06 19:06:00 +02:00 committed by doc-hex
parent 4f4d08c73f
commit f1ce63812c
3 changed files with 94 additions and 3 deletions

View File

@ -159,3 +159,36 @@ Commands:
get Get registered miniscript wallet by name.
ls List registered miniscript wallet names.
```
## Backup/Restore
```
Usage: ckcc backup [OPTIONS]
Creates 7z encrypted backup file after prompting user to remember a massive
passphrase. Downloads the AES-encrypted data backup and by default, saves
into current directory using a filename based on today's date.
Options:
-d, --outdir DIRECTORY Save into indicated directory (auto filename)
-o, --outfile filename.7z Name for backup file
--help Show this message and exit.
```
```
Usage: ckcc restore [OPTIONS] backup.7z
Uploads 7z encrypted backup file & starts backup restore process. User needs
to specify what kind of backup is being uploaded. Default is 7z encrypted
file with word-based password. Use -p/--password flag if your backup has
custom not word-based password. User is prompted to enter backup password on
the device.
Options:
-c, --plaintext Force plaintext restore. No need to use if file has proper
'.txt' suffix
-p, --password This backup has custom password. Not words.
-t, --tmp Force restoring backup as temporary seed. Only works for
seedless Coldcard.
--help Show this message and exit.
```

View File

@ -282,11 +282,14 @@ def real_file_upload(fd, dev, blksize=MAX_BLK_LEN, do_upgrade=False, do_reboot=T
help='Attempt multisig enroll using file')
@click.option('--miniscript', is_flag=True,
help='Attempt miniscript enroll using file')
def file_upload(filename, blksize, multisig, miniscript):
@click.option('--backup', is_flag=True,
help='Upload encrypted backup')
def file_upload(filename, blksize, multisig, miniscript, backup):
"""Send file to Coldcard (PSBT transaction or firmware)"""
if multisig and miniscript:
click.echo("Failed: Only one can be specified from miniscript/multisig")
if sum([multisig, miniscript, backup]) > 1:
# only 1 or None can be True
click.echo("Failed: Only one can be specified from miniscript/multisig/backup")
sys.exit(1)
# NOTE: mostly for debug/dev usage.
@ -298,6 +301,9 @@ def file_upload(filename, blksize, multisig, miniscript):
dev.send_recv(CCProtocolPacker.multisig_enroll(file_len, sha))
elif miniscript:
dev.send_recv(CCProtocolPacker.miniscript_enroll(file_len, sha), timeout=None)
elif backup:
dev.send_recv(CCProtocolPacker.restore_backup(file_len, sha), timeout=None)
@main.command('upgrade')
@ -614,6 +620,38 @@ def start_backup(outdir, outfile):
click.echo("Wrote %d bytes into: %s\nSHA256: %s" % (len(result), fn, str(b2a_hex(chk), 'ascii')))
@main.command('restore')
@click.argument('filename', type=click.File('rb'), metavar="backup.7z")
@click.option('-c', '--plaintext', is_flag=True,
help="Force plaintext restore. No need to use if file has proper '.txt' suffix")
@click.option('-p', '--password', is_flag=True,
help="This backup has custom password. Not words.")
@click.option('-t', '--tmp', is_flag=True,
help="Force restoring backup as temporary seed. Only works for seedless Coldcard.")
@display_errors
def restore_backup(filename, plaintext, password, tmp):
"""
Uploads 7z encrypted backup file & starts backup restore process. User needs to specify
what kind of backup is being uploaded. Default is 7z encrypted file with word-based password.
Use -p/--password flag if your backup has custom not word-based password.
User is prompted to enter backup password on the device.
"""
if not plaintext and filename.name.lower().endswith(".txt"):
plaintext = True
if plaintext and password:
# only 1 or None can be True
click.echo("Failed: Plaintext backup cannot have custom password.")
sys.exit(1)
with get_device() as dev:
file_len, sha = real_file_upload(filename, dev)
dev.send_recv(
CCProtocolPacker.restore_backup(file_len, sha, password, plaintext, tmp),
timeout=None)
@main.command('addr')
@click.argument('path', default=None, metavar='[m/1/2/3]', required=False)
@click.option('--segwit', '-s', is_flag=True, help='Show in segwit native (p2wpkh, bech32)')

View File

@ -67,6 +67,26 @@ class CCProtocolPacker:
# prompts user with password for encrypted backup
return b'back'
@staticmethod
def restore_backup(length, file_sha, custom_pwd=False, plaintext=False, tmp=False):
# backup file has to be already uploaded
# custom_pwd: (bool) .7z encrypted with custom password
# plaintext: (bool) clear-text (dev)
# tmp (bool) force load as tmp, effective only on seed-less CC
assert len(file_sha) == 32
assert not (custom_pwd and plaintext)
bf = 0
if custom_pwd:
bf |= 1
if plaintext:
bf |= 2
if tmp:
bf |= 4
return pack('<4sI32sB', b'rest', length, file_sha, bf)
@staticmethod
def encrypt_start(device_pubkey, version=USB_NCRY_V1):
supported_versions = [USB_NCRY_V1, USB_NCRY_V2]