keyteleport 0.1.0

A Rust implementation of the COLDCARD Key Teleport protocol
Documentation
use bitcoin::secp256k1::PublicKey;
use zeroize::{Zeroize as _, Zeroizing};

use crate::{
    DecodedPayload, Error, NumericCode, ReceiverPacket, Result, SenderPacket, TeleportPassword,
    crypto::{self, EphemeralPrivateKey, SessionKey},
    payload::Payload,
};

/// A receiver-side KeyTeleport session
#[derive(Debug)]
pub struct ReceiverSession {
    private_key: EphemeralPrivateKey,
}

impl ReceiverSession {
    /// Creates a receiver session with a new ephemeral private key
    pub fn new() -> Self {
        Self { private_key: EphemeralPrivateKey::generate() }
    }

    /// Restores a receiver session from its ephemeral private key
    pub fn from_private_key_bytes(bytes: [u8; 32]) -> Result<Self> {
        Ok(Self { private_key: EphemeralPrivateKey::from_bytes(bytes)? })
    }

    /// Returns the ephemeral private key bytes for session persistence
    pub fn private_key_bytes(&self) -> [u8; 32] {
        self.private_key.expose_bytes()
    }

    /// Creates the receiver request to share with a sender
    pub fn request(&self) -> Result<ReceiveRequest> {
        let (numeric_code, payload) = crypto::generate_receiver_packet(&self.private_key)?;

        Ok(ReceiveRequest { numeric_code, packet: ReceiverPacket::new(payload.to_vec())? })
    }

    /// Decrypts the outer layer of a sender packet
    pub fn decode_step1(&self, packet: &SenderPacket) -> Result<PendingPayload> {
        let sender_pubkey = PublicKey::from_slice(packet.sender_pubkey_bytes())?;
        let session_key = self.private_key.session_key(&sender_pubkey);
        let inner = session_key.decrypt_outer(packet.encrypted_body())?;

        Ok(PendingPayload { session_key, inner })
    }

    /// Decrypts and decodes a sender packet
    pub fn decode(
        &self,
        packet: &SenderPacket,
        password: &TeleportPassword,
    ) -> Result<DecodedPayload> {
        self.decode_step1(packet)?.complete(password)
    }
}

impl Default for ReceiverSession {
    fn default() -> Self {
        Self::new()
    }
}

/// A receiver request containing the numeric code and packet to share
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ReceiveRequest {
    /// The numeric code needed by the sender
    pub numeric_code: NumericCode,
    /// The receiver packet needed by the sender
    pub packet: ReceiverPacket,
}

/// A sender payload awaiting password-based decryption
#[derive(Debug)]
pub struct PendingPayload {
    session_key: SessionKey,
    inner: Vec<u8>,
}

impl PendingPayload {
    /// Decrypts and decodes the payload with the transfer password
    pub fn complete(mut self, password: &TeleportPassword) -> Result<DecodedPayload> {
        let noid_key = password.expose_bytes();
        let paranoid_key = self.session_key.paranoid_key(noid_key);
        let plaintext = Zeroizing::new(crypto::decrypt_inner(&paranoid_key, &self.inner)?);
        let decoded = DecodedPayload::decode(&plaintext);

        self.inner.zeroize();

        decoded
    }
}

impl Drop for PendingPayload {
    fn drop(&mut self) {
        self.inner.zeroize();
    }
}

pub(crate) fn encode_for_sender(
    sender_private_key: EphemeralPrivateKey,
    receiver_public_key: &PublicKey,
    password: &TeleportPassword,
    payload: Payload,
) -> Result<SenderPacket> {
    let sender_public_key = sender_private_key.public_key();
    let session_key = sender_private_key.session_key(receiver_public_key);
    let noid_key = password.expose_bytes();
    let paranoid_key = session_key.paranoid_key(noid_key);
    let plaintext = payload.encode()?;
    let inner = crypto::encrypt_inner(&paranoid_key, &plaintext);
    let outer = session_key.encrypt_outer(&inner);
    let mut packet = Vec::with_capacity(33 + outer.len());
    packet.extend_from_slice(&sender_public_key.serialize());
    packet.extend_from_slice(&outer);

    SenderPacket::new(packet).map_err(|_| Error::InvalidSenderPacket)
}