# 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.