Documentation
// SPDX-FileCopyrightText: Heiko Schaefer <heiko@schaefer.name>
// SPDX-License-Identifier: MIT OR Apache-2.0

//! An example program that generates and verifies a data signature.
//!
//! In more detail, this program:
//!
//! - Generates a new OpenPGP TSK (Transferable Secret Key),
//! - Signs a payload with it (as a detached signature),
//!
//! - Derives a certificate (transferable public key) from the TSK
//! - Verifies the signature again.
//!
//! The program prints the signature and certificate to stdout for inspection.
//!
//! These printed artifacts can be tested in external SOP programs.
//! E.g. by copying the armored detached signature block into a file `sig`,
//! the armored certificate block into a `cert` file, and then doing:
//!
//! ```
//! $ echo -n "hello world" | SOP verify sig cert
//! 2026-05-01T11:27:10Z 97af5c7617b48b72c401e1d4995e3ea9624fe7fe85c4c972da6ba0f1bdc0572b 97af5c7617b48b72c401e1d4995e3ea9624fe7fe85c4c972da6ba0f1bdc0572b mode:binary {"signers":["cert"]}
//! ```

use std::io::stdout;

use minipgp6::{keygen::generate_tsk, locking::try_unlock_with_passwords};
use minipgp6_armor::{DataType, write::Armorer};
use minipgp6_packet::{
    Serialize,
    transferable::{Tpk, Tsk},
    types::{algorithm::PublicKeyAlgorithm, time::Timestamp},
};
use minipgp6_sign::{data, data::DetachedSignature};
use minipgp6_validity::Checked;
use rand::thread_rng;

fn sign(tsk: &Tsk, data: &[u8]) -> Result<DetachedSignature, Box<dyn std::error::Error>> {
    // Pick the component key(s) that are semantically valid for issuing data signatures
    let checked = Checked::from(tsk);
    let signers = checked.valid_for_data_signing();

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

    // Produce detached signature(s) over the payload
    let signatures = DetachedSignature::sign_data(thread_rng(), data, &unlocked_signers)?;

    assert_eq!(signatures.len(), 1);

    if let Some(sig) = signatures.into_iter().next() {
        Ok(sig)
    } else {
        Err("No signature found".into())
    }
}

fn verify(
    cert: &Tpk,
    signature: DetachedSignature,
    data: &[u8],
) -> Result<(), Box<dyn std::error::Error>> {
    let checked = Checked::from(cert);
    let verifiers = checked.valid_for_data_verification_at(Timestamp::now());

    let digests = DetachedSignature::digests(&[signature], data)?;
    let valid = data::check_digests(&digests, &verifiers)?;
    for (_signature, signer) in valid {
        eprintln!("- valid signature by {}", signer.fingerprint())
    }

    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::X25519),
        &["alice"],
        None,
    )?;

    eprintln!("Signing the payload {:?}", PAYLOAD);
    eprintln!();

    let signature = sign(&tsk, PAYLOAD.as_bytes())?;

    eprintln!("Signature:");
    let mut out = Armorer::new(stdout(), DataType::Signature)?;
    signature.to_writer(&mut out)?;
    out.finalize()?;

    eprintln!();

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

    eprintln!("OpenPGP certificate:");
    let mut out = Armorer::new(stdout(), DataType::Public)?;
    cert.to_writer(&mut out)?;
    out.finalize()?;

    eprintln!();

    eprintln!("Verification result:");
    verify(&cert, signature, PAYLOAD.as_bytes())?;

    Ok(())
}