Documentation
//! An example program that encrypts and decrypts a message.
//! Encryption uses a composite PQC subkey.
//!
//! In more detail, this program:
//!
//! - Generates a new OpenPGP TSK (Transferable Secret Key). The TSK uses a classic Ed25519 primary
//!   key and an ML-KEM-768+X25519 encryption subkey.
//! - Derives a certificate (transferable public key) from the TSK
//!
//! - Encrypts a message to the certificate
//! - Decrypts the message again.
//!
//! The program prints the TSK and the encrypted message to stdout for inspection.
//!
//! These printed artifacts can be tested in external SOP programs.
//! E.g. by copying the armored TSK block into a file `tsk`,
//! the armored message block into a `msg` file, and then doing:
//!
//! ```
//! $ cat msg | SOP decrypt tsk
//! hello world
//! ```
//!
//! Note that decryption of this example message requires a PQC-capable SOP!
//! (E.g. `msop` or `cargo install --features=draft-pqc rsop`)

use std::{
    io,
    io::{BufReader, Read, stdout},
};

use minipgp6::{keygen::generate_tsk, locking::try_unlock_with_passwords};
use minipgp6_armor::{DataType, read::Dearmorer, write::Armorer};
use minipgp6_encrypt::message::{MessageDecryptor, MessageEncryptor};
use minipgp6_packet::{
    packet::literal::LiteralWriter,
    sequence::{Output, PacketSequence},
    transferable::{Tpk, Tsk},
    types::PublicKeyAlgorithm,
};
use minipgp6_validity::Checked;
use rand::thread_rng;

fn encrypt(cert: &Tpk, mut data: &[u8]) -> Result<String, Box<dyn std::error::Error>> {
    let mut rng = thread_rng();

    // Pick the component key(s) that are semantically valid for encryption
    let checked = Checked::from(cert);
    let encryptors = checked.valid_for_encryption();

    // Produce encrypted message from the payload
    let mut armored = Vec::new();

    {
        let mut armorer = Armorer::new(&mut armored, DataType::Message)?;

        {
            let mut encryptor = MessageEncryptor::new(&mut rng, &mut armorer, &encryptors)?;
            let mut literal = LiteralWriter::new(&mut encryptor)?;
            let _ = io::copy(&mut data, &mut literal)?;
        }
        armorer.finalize()?;
    }

    Ok(String::from_utf8(armored)?)
}

fn decrypt(tsk: &Tsk, armored: String) -> Result<(), Box<dyn std::error::Error>> {
    let checked = Checked::from(tsk);
    let decryptors = checked.valid_for_decryption();

    // Obtain unlocked representations of the signer(s)
    let unlocked_decryptors: Vec<_> = decryptors
        .into_iter()
        .flat_map(|s| try_unlock_with_passwords(s, &[]))
        .collect();

    let dearmor = Dearmorer::new(armored.as_bytes())?;
    let sequence = PacketSequence::new(BufReader::new(dearmor));
    let message = MessageDecryptor::new(sequence)?;

    // A streaming reader that yields the plaintext content of the seipd packet, if successful
    let mut payload_decryptor = message.decrypt(&unlocked_decryptors)?;

    let mut plaintext: Vec<u8> = Vec::new();

    let decrypted_sequence = PacketSequence::new(BufReader::new(&mut payload_decryptor));
    match decrypted_sequence.next() {
        Ok(Output::Literal(mut lit)) => {
            let _ = lit.read_to_end(&mut plaintext)?;
        }

        _ => return Err("failed to decrypt".into()),
    }

    eprintln!("Decrypted message: {:?}", String::from_utf8(plaintext)?);

    Ok(())
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    const PAYLOAD: &str = "hello world";

    let mut rng = thread_rng();

    // Generate a new OpenPGP transferable secret key (TSK)
    let tsk = generate_tsk(
        &mut rng,
        PublicKeyAlgorithm::Ed25519,
        Some(PublicKeyAlgorithm::MlKem768X25519),
        &["alice"],
        None,
    )?;

    // Derive the public key (certificate) from the Tsk
    let cert = Tpk::from(tsk.clone());

    let encrypted = encrypt(&cert, PAYLOAD.as_bytes())?;

    eprintln!("Encrypted message:");
    eprintln!("{encrypted}");
    eprintln!();

    eprintln!("OpenPGP TSK:");
    let mut out = Armorer::new(stdout(), DataType::Private)?;
    tsk.to_writer(&mut out)?;
    out.finalize()?;
    eprintln!();

    decrypt(&tsk, encrypted)?;

    Ok(())
}