mtp/crypto
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-07-03 16:43:29 +02:00
..
src CryptoUtil 2026-07-03 16:43:29 +02:00
Cargo.lock Gitignores 2026-06-27 02:00:42 +02:00
Cargo.toml CryptoUtil 2026-07-03 16:43:29 +02:00
README.md Crypto Readme 2026-07-02 23:22:26 +02:00

mtp-crypto

Cryptographic primitives for the MTP protocol. Classical and post-quantum.

Features

Feature Primitives
default XChaCha20-Poly1305, Ed25519, ML-DSA-65, HKDF-SHA-256, SHA-256
full default + AES-256-GCM
pqc ML-KEM-768+X25519 hybrid KEM

ML-DSA-65 is enabled by default so dual-signature support is always available without a separate PQC feature flag in protocol crates.

AEAD

XChaCha20-Poly1305 (default) and AES-256-GCM (full feature). Nonce is prepended to ciphertext.

use mtp_crypto::{ChaCha20Poly1305, AeadEncrypt, AeadDecrypt};

let cipher = ChaCha20Poly1305::new([0u8; 32]);
let ct = cipher.encrypt(b"hello", b"aad")?;
let pt = cipher.decrypt(&ct, b"aad")?;

Signatures

Ed25519

use mtp_crypto::{Ed25519Signer, SignatureScheme};

let (signer, sk, pk) = Ed25519Signer::generate();
let sig = signer.sign(b"message")?;
signer.verify(b"message", &sig)?;

ML-DSA-65

use mtp_crypto::{MlDsaSigner, SignatureScheme};

let (signer, sk, pk) = MlDsaSigner::generate();
let sig = signer.sign(b"message")?;
signer.verify(b"message", &sig)?;

// Load from stored bytes
let signer = MlDsaSigner::new(&sk, &pk)?;

Dual signatures

use mtp_crypto::{sign_dual, DualSignature, Ed25519Signer, MlDsaSigner};

let (ed_signer, _, _) = Ed25519Signer::generate();
let (ml_signer, _, _) = MlDsaSigner::generate();
let dual = sign_dual(ed_signer.signing_key(), ml_signer.signing_key(), b"msg");
dual.verify(ed_signer.verifying_key(), ml_signer.verifying_key(), b"msg")?;

Hybrid KEM

X25519 + ML-KEM-768. 64-byte shared secret. Feed into HKDF before use.

use mtp_crypto::HybridKem;

let (sk, pk) = HybridKem::generate_keypair();
let enc = HybridKem::encapsulate(&pk)?;
let ss = HybridKem::decapsulate(&sk, &enc.ciphertext)?;
assert_eq!(enc.shared_secret, ss);

Encrypted containers

Self-describing encrypted blobs with algorithm selection via EncryptionType. Each blob begins with a marking byte so recipients can decrypt without out-of-band agreement.

use mtp_crypto::{EncryptionType, Keyring, encrypt_for, decrypt_with};

let kr = Keyring::generate();
let blob = encrypt_for(EncryptionType::MlKemChaCha20Poly1305, &kr.public_key_bundle(), b"data", b"aad")?;
let pt = decrypt_with(&blob, &kr, b"aad")?;

Multi-recipient encryption

Encrypt a payload for multiple recipients using a content-encryption key wrapped per-recipient via Hybrid KEM.

use mtp_crypto::{Keyring, encrypt_multi, decrypt_multi};

let alice = Keyring::generate();
let bob = Keyring::generate();
let msg = encrypt_multi(b"secret", b"aad", &[alice.public_key_bundle(), bob.public_key_bundle()])?;
let pt = decrypt_multi(&msg, b"aad", &alice)?;

Authentication handshake

Canonical domain-separated payloads for the challenge-response handshake.

use mtp_crypto::auth::{challenge_payload, login_proof_payload, register_proof_payload, host_final_payload};

Each payload type uses a distinct domain tag to prevent replay across protocol steps.

KDF

use mtp_crypto::{hkdf_expand, hkdf_extract, derive_encryption_key};

let key = derive_encryption_key(b"ikm", b"salt", b"context")?;
let prk = hkdf_extract(b"ikm", b"salt");

Hashing

use mtp_crypto::{sha256, sha256_double, Sha256Hasher};

let h = sha256(b"data");
let h2 = sha256_double(b"data");

let mut hasher = Sha256Hasher::new();
hasher.update(b"da");
hasher.update(b"ta");
let h3 = hasher.finalize();

Key types

Type Secret Zeroized
EncryptionPrivateKey KEM/ECDH secret Yes
EncryptionPublicKey KEM/ECDH public No
SignaturePrivateKey Classical signing key Yes
SignaturePublicKey Classical verifying key No
KemPrivateKey Hybrid KEM secret Yes
KemPublicKey Hybrid KEM public No
SignaturePqPrivateKey PQC signing key Yes
SignaturePqPublicKey PQC verifying key No

Keyring holds all six keys (hybrid KEM + PQ sig + classical sig) plus generate(), to_bytes(), and from_bytes() for serialization. PublicKeyBundle holds the three public keys for distribution.

Feature flags

[dependencies]
mtp-crypto = { path = "../crypto" }                          # classical + ML-DSA
mtp-crypto = { path = "../crypto", features = ["pqc"] }       # adds hybrid KEM
mtp-crypto = { path = "../crypto", features = ["full", "pqc"] } # adds AES-256-GCM + hybrid KEM
mtp-crypto = { path = "../crypto", features = ["serde"] }      # serde support
mtp-crypto = { path = "../crypto", features = ["wasm"] }       # WASM compat