
# Key Teleport

Purpose: Send a small quantity of very secret data between two COLDCARD Q systems, with
no risk of anything in the middle learning the secret.

Method: ECDH and AES-256-CTR plus an extra wrapping layer, transmitted over a mixture of
NFC, passive websites, and QR/BBQr codes.

# Protocol Overview

## Steps

- Receiver picks an EC keypair, stores it in settings, and publishes the pubkey via a QR/NFC
- The pubkey is encrypted by a short 8-digit numeric code, which should be
  sent by a different channel.
- Sender gets QR and numeric code, picks own keypair, and does ECDH to arrive at a
  shared session key
- Sender picks a human-readable secret which is independent of anything else (P key)
- The secret data (perhaps a seed phrase, XPRV, secure note, full backup, etc) is
  AES-256-CTR encrypted with P key, then encrypted + MAC added with session key
- Data packet is sent to receiver (via BBQr), who can reconstruct the session key via ECDH
- Prompt user for the P key to finish decoding
- Decoded secret value is saved to Seed Vault or secure notes as appropriate
- Receiver destroys EC keypair used in transfer

### When used for PSBT Multisig

- No action required on receiver
- Sender uses the pubkey derived from pre-shared XPUB involved in the multisig wallet.
- Same steps, but drops immediately into signing process when decoded correctly

## Notes and Limitations

- max 4k (after encoding) of data is possible due to HTTP limitations
- all transfers are "data typed" and decode only on COLDCARD
- Q model is required due to the use of QR codes to ultimately get data into the COLDCARD


# Details

## Data Type Codes

The first byte encodes what the package contents (under all the encryption).

- `s` - 12/18/24 words/raw master/xprv - 17-72 bytes follow, encoded in an internal format
- `x` - XPRV mode, full details - 4 bytes (XPRV) + base58 *decoded* binary-XPRV follows
- `n` - one or many notes export (JSON array)
- `v` - seed vault export (JSON: one secret key but includes name, source of key)
- `p` - binary PSBT to be signed, perhaps multisig but not required.
- `b` - complete system backup file (text lines, internal format)

## QR details

BBQr is always used for the QR's involved in this process, even if
they are short enough for a normal QR code. Because the BBQr is
being generated by the COLDCARD embedded firmware, it will not be
compressed and will always be Base32 encoded.

New type codes for BBQr are defined for the purposes of this application:

- `R` contains `(pubkey)` ... begins the process from receiver; compressed pubkey is 33 bytes
- `S` contains `(pubkey)(data)` ... data from sender; first 33 bytes are sender's pubkey
- `E` for Multisig PSBT: `(randint)(data)` ... randint (4 byte nonce) indicates which 
  derived subkey from pre-shared xpub associated with receiver

All the data is encrypted with the exception randint. Keep in mind
this is a nonce value picked uniquely for each transfer. The
receiver's pubkey is only weakly encrypted by the 8-digit numeric
password, but is also a nonce effectively.

### PSBT Key Selection

When sending PSBT data, a nonce is picked at random by the sender
in range: `0..(2^28)`

This nonce is called `randint`. The receiver's pubkey will be

    .../20250317/(randint)

where `...` is the derivation used in the multisig wallet for the co-signer who will
receive the package. The sender's keypair has the same sub key path assuming all
co-signers have same derivation path from root (not required).

Because both the sender and receiver already have each other's XPUB they can derive
the appropriate pubkeys (and privkey for their side) without communicating 
more than `randint`. The sending COLDCARD will pick a new random value each time.

When receiving a multisig PSBT encrypted this way, the receiver does not need
to do any setup (nor numeric password) and can receive a QR code at any time.
This works because the shared multisig wallet is already setup. Receiver will
take the nonce value (randint) and seach all pre-defined multisig wallets for
any pubkey that can decrypt the package successfully (based on checksum inside
first layer of ECDH encryption).

The next layer of encryption (paranoid password) is unchanged.

## Encryption Details

AES-256-CTR is used exclusively. Session key is picked via ECDH with final
key value being the SHA256 over 64 bytes of coordinate X (concat) Y.

While ECDH is enough to assure privacy from men in the middle, we
add an additional layer of encryption. We call this the "paranoid key" internally
and in the UX it is called "Teleport Password".

The user sees a random 8-character password, generated as a random 40-bit value, but
shown in Base32 (8 chars) for the human to enter. We apply PBKDF2-SHA512 with
an iteration count of 5000 to stretch that to 512 bits, of which we use half.
The session key is used as the key for the KDF, and the entered value as salt.

- ECDH arrives at session key
- decrypt (AES-256-CTR) the binary body of message
- verify checksum:
    - final 2 bytes should be `== SHA256(decrypted body[0:-2])[-2:]`
    - if not, corruption, truncation, or wrong keys
- if that decryption is correct, then prompt user for the paranoid key (8 chars)
- stretch that value using session key and 5000 iterations of PBKDF2-SHA512
- use upper 256 bits and run AES-256-CTR again
- same checksum of 2 bytes of SHA256 are found inside after decryption

Encryption adds 4 bytes of overhead because of these MAC values, 
but should catch truncation and bitrot. There are no other
protections against truncation as length data is not transmitted.

# Receiver Password

When the teleport process is started, the receiver shares his pubkey
as QR. However, we also show an 8-digit numeric password. The
purpose of this is force the receiver to share this separately from
the pubkey QR on another channel. The code is randomly picked, but
only represents about 26 bits of entropy and is stretched with
a single round of SHA256 before being used as a AES-256-CTR key
to decrypt the pubkey. No checksum verifies correct
decryption, so any code is accepted, and will with near-50% odds,
decrypt to a valid pubkey.

When the sender is given the receiver's pubkey via QR code, it
prompts for the numeric code and uses it to decrypt the pubkey.
Thus a MiTM who injects their pubkey will be detected and blocked.

The "paranoid key" serves the same role in the other direction but
it is Base32 character set, so it will not look similar or be
confusing as to its purpose.

# Web Component

In order to "teleport" the contents of a QR code over NFC, we will
publish a static website directly from an open Github repository.
The single-page website contains javascript code which looks at the
"hash" part of the incoming URL (`window.location.hash`) and if it
meets the requirements, renders a large QR. The QR data must look like
a correctly-encoded BBQr with one of the 3 type-codes above (`R` `S` or `E`).
Otherwise the website could render any QR, which we don't want to
support.

The page will offer "copy to clipboard" features for the data inside
the QR as a URL (ie. same URL as shown) and as an image and of course,
the COLDCARD Q can scan from the web browser screen itself.

When the BBQr data is larger than comfortable for a single QR, the
website can split into a multi-frame BBQr. The website can
do this without understanding the contents of the BBQr data (all
of which is encrypted). Download options will be provided for
single-frame QR, animated PNG, and "stacked BBQr" (a single tall
PNG with each QR frame stacked).

On the COLDCARD side, when NFC is tapped, it will offer a long URL
to this site with the data to be transferred "after the hash".  This
is optional since the QR can be shown on the Q itself, and would
pass the same data.

Since the website is running on Github, Coinkite does not have
access to IP addresses or other access log details. Because the data for
teleport is "after the hash" it is never sent to Github's servers
but remains in the browser only. All JS resources referenced by the
webpage will have content hashes applied to prevent interference,
and the site will be served over SSL.

Visit [keyteleport.com](https://keyteleport.com/), or an
[example small QR](https://keyteleport.com/#B$2R0100VHT2AGUUH7KUZUUSTOWOIWHJX3XM7GA2N4BHQOXDFHXLVHVA7K6ZO)
and [view source code](https://github.com/coinkite/keyteleport.com).

# UX Details

- When the receive process is started by the user, a pubkey is picked
  and stored, so that they can come back later (after a power cycle)
  and make use of the data encoded by the sender. However once a package
  is decoded successfully, that key is deleted.

- Sender must start by scanning the QR from a receiver. Then can pick what
  to send, from secure notes to seeds and so on.

- For PSBT multisig, user must pick a single co-signer (who hasn't already
  signed) and the QR is prepared for that receiver. Because we
  cannot do arbitary combining, it's best if the next signer continues
  to teleport the updated PSBT to further signers. In other words,
  a daisy-chain pattern is prefered to a star pattern. The signer
  who completes the Mth (of N) signature will be able to finalize
  the transaction, and ideally with PushTx feature, broadcast it.

# Security Comments

## Such short passwords?

We are using 8-character passwords because we want them to be
practical to share over non-digital channels such as a voice phone
call, or hand-written note.

It is very important to remind users that the passwords should be sent
by a different channel from the QR itself. Best is to call up your
other party and say the letters to them directly.

## Is it safe to save image of QR to cloud?

Yes, this seems safe. Of course, if you can control it, perhaps not
a risk to accept... but the QR is encrypted via ECDH using a key
that is forgotten after the transfer, so forward privacy is protected.
Also your cloud service (or photo roll, chat app log, etc) will not
have the 8-character password which is also required unpack the secrets.

The QR codes themselves are fully random and do not reveal the
identity of your COLDCARD, your on chain funds or anything linked
to you.
