General & Crypto

This commit is contained in:
Alex Emmet 2026-06-20 15:25:36 +02:00
commit 02f94993c7
27 changed files with 1881 additions and 47 deletions

View file

@ -1,6 +1,21 @@
[package]
name = "mtp-crypto"
version = "0.1.0"
version = "0.2.0"
edition = "2024"
[dependencies]
chacha20poly1305 = { version = "0.10", optional = true }
aes-gcm = { version = "0.10", optional = true }
ed25519-dalek = { version = "2.1", optional = true, features = ["pkcs8", "pem"] }
hkdf = { version = "0.12", optional = true }
sha2 = { version = "0.10", optional = true }
zeroize = { version = "1.7", features = ["derive"] }
rand_core = { version = "0.6", features = ["getrandom"] }
getrandom = "0.2"
mlkem-tls = { version = "0.2", optional = true }
ml-dsa = { version = "0.0.4", optional = true }
[features]
default = ["chacha20poly1305", "ed25519-dalek", "hkdf", "sha2"]
full = ["chacha20poly1305", "aes-gcm", "ed25519-dalek", "hkdf", "sha2"]
pqc = ["mlkem-tls", "ml-dsa"]

113
crypto/README.md Normal file
View file

@ -0,0 +1,113 @@
# mtp-crypto
Cryptographic primitives for the MTP protocol. Classical and post-quantum.
## Features
| Feature | Primitives | Status |
|---------|-----------|--------|
| `default` | XChaCha20-Poly1305, Ed25519, HKDF-SHA-256, SHA-256 | Classical |
| `full` | default + AES-256-GCM | Classical |
| `pqc` | ML-KEM-768+X25519 hybrid KEM, ML-DSA-65 | Post-quantum |
## AEAD
XChaCha20-Poly1305 (default) and AES-256-GCM (`full` feature). Nonce is prepended to ciphertext.
```rust
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 (classical)
```rust
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 (post-quantum, requires `pqc`)
```rust
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 (requires `pqc`)
```rust
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 (requires `pqc`)
X25519 + ML-KEM-768. 64-byte shared secret. Feed into HKDF before use.
```rust
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);
```
## KDF
```rust
use mtp_crypto::{hkdf_expand, derive_encryption_key};
let key = derive_encryption_key(b"ikm", b"salt", b"context")?;
```
## Hashing
```rust
use mtp_crypto::{sha256, sha256_double};
let h = sha256(b"data");
let h2 = sha256_double(b"data");
```
## 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 |
`KeyGroup` holds classical keys; `Keyring` holds all six (hybrid KEM + PQ sig + classical sig).
## Feature flags
```toml
[dependencies]
mtp-crypto = { path = "../crypto" } # classical
mtp-crypto = { path = "../crypto", features = ["pqc"] } # post-quantum
mtp-crypto = { path = "../crypto", features = ["full", "pqc"] } # all
```

171
crypto/src/aead.rs Normal file
View file

@ -0,0 +1,171 @@
use crate::error::CryptoError;
#[cfg(any(feature = "chacha20poly1305", feature = "aes-gcm"))]
use rand_core::OsRng;
#[cfg(any(feature = "chacha20poly1305", feature = "aes-gcm"))]
use rand_core::RngCore;
pub trait AeadEncrypt {
fn encrypt(&self, plaintext: &[u8], aad: &[u8]) -> Result<Vec<u8>, CryptoError>;
}
pub trait AeadDecrypt {
fn decrypt(&self, ciphertext: &[u8], aad: &[u8]) -> Result<Vec<u8>, CryptoError>;
}
pub trait AeadCipher: AeadEncrypt + AeadDecrypt {
fn key_size() -> usize;
}
#[cfg(feature = "chacha20poly1305")]
pub struct ChaCha20Poly1305 {
key: [u8; 32],
}
#[cfg(feature = "chacha20poly1305")]
impl ChaCha20Poly1305 {
pub fn new(key: [u8; 32]) -> Self {
Self { key }
}
}
#[cfg(feature = "chacha20poly1305")]
impl AeadEncrypt for ChaCha20Poly1305 {
fn encrypt(&self, plaintext: &[u8], aad: &[u8]) -> Result<Vec<u8>, CryptoError> {
use chacha20poly1305::aead::{Aead, KeyInit, Payload};
use chacha20poly1305::XChaCha20Poly1305;
use chacha20poly1305::XNonce;
let key = chacha20poly1305::Key::from_slice(&self.key);
let cipher = XChaCha20Poly1305::new(key);
let mut nonce = [0u8; 24];
OsRng.fill_bytes(&mut nonce);
let nonce_ref = XNonce::from_slice(&nonce);
let payload = Payload {
msg: plaintext,
aad,
};
let mut ciphertext = cipher
.encrypt(nonce_ref, payload)
.map_err(|_| CryptoError::EncryptionFailed)?;
let mut out = Vec::with_capacity(nonce.len() + ciphertext.len());
out.extend_from_slice(&nonce);
out.append(&mut ciphertext);
Ok(out)
}
}
#[cfg(feature = "chacha20poly1305")]
impl AeadDecrypt for ChaCha20Poly1305 {
fn decrypt(&self, ciphertext: &[u8], aad: &[u8]) -> Result<Vec<u8>, CryptoError> {
use chacha20poly1305::aead::{Aead, KeyInit, Payload};
use chacha20poly1305::XChaCha20Poly1305;
use chacha20poly1305::XNonce;
if ciphertext.len() < 24 {
return Err(CryptoError::InvalidNonceLength);
}
let (nonce, ct) = ciphertext.split_at(24);
let key = chacha20poly1305::Key::from_slice(&self.key);
let cipher = XChaCha20Poly1305::new(key);
let nonce_ref = XNonce::from_slice(nonce);
let payload = Payload {
msg: ct,
aad,
};
cipher
.decrypt(nonce_ref, payload)
.map_err(|_| CryptoError::DecryptionFailed)
}
}
#[cfg(feature = "chacha20poly1305")]
impl AeadCipher for ChaCha20Poly1305 {
fn key_size() -> usize {
32
}
}
#[cfg(feature = "aes-gcm")]
pub struct Aes256Gcm {
key: [u8; 32],
}
#[cfg(feature = "aes-gcm")]
impl Aes256Gcm {
pub fn new(key: [u8; 32]) -> Self {
Self { key }
}
}
#[cfg(feature = "aes-gcm")]
impl AeadEncrypt for Aes256Gcm {
fn encrypt(&self, plaintext: &[u8], aad: &[u8]) -> Result<Vec<u8>, CryptoError> {
use aes_gcm::aead::{Aead, KeyInit, Payload};
use aes_gcm::Aes256Gcm as AesGcmInner;
use aes_gcm::Nonce;
let key = aes_gcm::Key::<AesGcmInner>::from_slice(&self.key);
let cipher = AesGcmInner::new(key);
let mut nonce = [0u8; 12];
OsRng.fill_bytes(&mut nonce);
let nonce_ref = Nonce::from_slice(&nonce);
let payload = Payload {
msg: plaintext,
aad,
};
let mut ciphertext = cipher
.encrypt(nonce_ref, payload)
.map_err(|_| CryptoError::EncryptionFailed)?;
let mut out = Vec::with_capacity(nonce.len() + ciphertext.len());
out.extend_from_slice(&nonce);
out.append(&mut ciphertext);
Ok(out)
}
}
#[cfg(feature = "aes-gcm")]
impl AeadDecrypt for Aes256Gcm {
fn decrypt(&self, ciphertext: &[u8], aad: &[u8]) -> Result<Vec<u8>, CryptoError> {
use aes_gcm::aead::{Aead, KeyInit, Payload};
use aes_gcm::Aes256Gcm as AesGcmInner;
use aes_gcm::Nonce;
if ciphertext.len() < 12 {
return Err(CryptoError::InvalidNonceLength);
}
let (nonce, ct) = ciphertext.split_at(12);
let key = aes_gcm::Key::<AesGcmInner>::from_slice(&self.key);
let cipher = AesGcmInner::new(key);
let nonce_ref = Nonce::from_slice(nonce);
let payload = Payload {
msg: ct,
aad,
};
cipher
.decrypt(nonce_ref, payload)
.map_err(|_| CryptoError::DecryptionFailed)
}
}
#[cfg(feature = "aes-gcm")]
impl AeadCipher for Aes256Gcm {
fn key_size() -> usize {
32
}
}

38
crypto/src/error.rs Normal file
View file

@ -0,0 +1,38 @@
use std::fmt;
#[derive(Debug, Clone)]
pub enum CryptoError {
EncryptionFailed,
DecryptionFailed,
InvalidKeyLength,
InvalidNonceLength,
InvalidSignature,
SigningFailed,
VerificationFailed,
KeyGenerationFailed,
KdfError,
KemEncapsulationFailed,
KemDecapsulationFailed,
UnknownAlgorithm,
}
impl fmt::Display for CryptoError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CryptoError::EncryptionFailed => write!(f, "encryption failed"),
CryptoError::DecryptionFailed => write!(f, "decryption failed"),
CryptoError::InvalidKeyLength => write!(f, "invalid key length"),
CryptoError::InvalidNonceLength => write!(f, "invalid nonce length"),
CryptoError::InvalidSignature => write!(f, "invalid signature"),
CryptoError::SigningFailed => write!(f, "signing failed"),
CryptoError::VerificationFailed => write!(f, "verification failed"),
CryptoError::KeyGenerationFailed => write!(f, "key generation failed"),
CryptoError::KdfError => write!(f, "KDF error"),
CryptoError::KemEncapsulationFailed => write!(f, "KEM encapsulation failed"),
CryptoError::KemDecapsulationFailed => write!(f, "KEM decapsulation failed"),
CryptoError::UnknownAlgorithm => write!(f, "unknown algorithm"),
}
}
}
impl std::error::Error for CryptoError {}

29
crypto/src/hash.rs Normal file
View file

@ -0,0 +1,29 @@
use sha2::Digest;
pub fn sha256(data: &[u8]) -> [u8; 32] {
let mut hasher = sha2::Sha256::new();
hasher.update(data);
let result = hasher.finalize();
result.into()
}
pub fn sha256_double(data: &[u8]) -> [u8; 32] {
sha256(&sha256(data))
}
pub struct Sha256Hasher(sha2::Sha256);
impl Sha256Hasher {
pub fn new() -> Self {
Self(sha2::Sha256::new())
}
pub fn update(&mut self, data: &[u8]) {
self.0.update(data);
}
pub fn finalize(self) -> [u8; 32] {
let result = self.0.finalize();
result.into()
}
}

34
crypto/src/kdf.rs Normal file
View file

@ -0,0 +1,34 @@
use crate::error::CryptoError;
use hkdf::Hkdf;
use sha2::Sha256;
pub fn hkdf_expand(
ikm: &[u8],
salt: &[u8],
info: &[u8],
okm_len: usize,
) -> Result<Vec<u8>, CryptoError> {
let hk = Hkdf::<Sha256>::new(Some(salt), ikm);
let mut okm = vec![0u8; okm_len];
hk.expand(info, &mut okm)
.map_err(|_| CryptoError::KdfError)?;
Ok(okm)
}
pub fn hkdf_extract(ikm: &[u8], salt: &[u8]) -> [u8; 32] {
let (_, hk) = Hkdf::<Sha256>::extract(Some(salt), ikm);
let mut okm = [0u8; 32];
hk.expand(&[], &mut okm).expect("hkdf expand failed");
okm
}
pub fn derive_encryption_key(
ikm: &[u8],
salt: &[u8],
context: &[u8],
) -> Result<[u8; 32], CryptoError> {
let key = hkdf_expand(ikm, salt, context, 32)?;
let mut out = [0u8; 32];
out.copy_from_slice(&key);
Ok(out)
}

45
crypto/src/kem.rs Normal file
View file

@ -0,0 +1,45 @@
use crate::error::CryptoError;
use crate::keypair::{KemPrivateKey, KemPublicKey};
pub struct Encapsulated {
pub ciphertext: Vec<u8>,
pub shared_secret: Vec<u8>,
}
#[cfg(feature = "mlkem-tls")]
pub struct HybridKem;
#[cfg(feature = "mlkem-tls")]
impl HybridKem {
pub fn generate_keypair() -> (KemPrivateKey, KemPublicKey) {
let (dk, ek) =
mlkem_tls::X25519MlKem768::keygen(&mut rand_core::OsRng);
(
KemPrivateKey::new(dk.as_bytes().to_vec()),
KemPublicKey::new(ek.as_bytes().to_vec()),
)
}
pub fn encapsulate(recipient_pk: &KemPublicKey) -> Result<Encapsulated, CryptoError> {
let ek = mlkem_tls::EncapsKey768::try_from(recipient_pk.as_bytes())
.map_err(|_| CryptoError::KemEncapsulationFailed)?;
let (ct, ss) =
mlkem_tls::X25519MlKem768::encapsulate(&ek, &mut rand_core::OsRng);
Ok(Encapsulated {
ciphertext: ct.as_bytes().to_vec(),
shared_secret: ss.as_bytes().to_vec(),
})
}
pub fn decapsulate(
recipient_sk: &KemPrivateKey,
ciphertext: &[u8],
) -> Result<Vec<u8>, CryptoError> {
let dk = mlkem_tls::DecapsKey768::try_from(recipient_sk.as_bytes())
.map_err(|_| CryptoError::KemDecapsulationFailed)?;
let ct = mlkem_tls::Ciphertext768Hybrid::try_from(ciphertext)
.map_err(|_| CryptoError::KemDecapsulationFailed)?;
let ss = mlkem_tls::X25519MlKem768::decapsulate(&dk, &ct);
Ok(ss.as_bytes().to_vec())
}
}

212
crypto/src/keypair.rs Normal file
View file

@ -0,0 +1,212 @@
use zeroize::{Zeroize, ZeroizeOnDrop};
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct EncryptionPrivateKey(Vec<u8>);
impl EncryptionPrivateKey {
pub fn new(bytes: Vec<u8>) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
impl From<Vec<u8>> for EncryptionPrivateKey {
fn from(bytes: Vec<u8>) -> Self {
Self(bytes)
}
}
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct SignaturePrivateKey(Vec<u8>);
impl SignaturePrivateKey {
pub fn new(bytes: Vec<u8>) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
impl From<Vec<u8>> for SignaturePrivateKey {
fn from(bytes: Vec<u8>) -> Self {
Self(bytes)
}
}
#[derive(Clone)]
pub struct EncryptionPublicKey(Vec<u8>);
impl EncryptionPublicKey {
pub fn new(bytes: Vec<u8>) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
impl From<Vec<u8>> for EncryptionPublicKey {
fn from(bytes: Vec<u8>) -> Self {
Self(bytes)
}
}
#[derive(Clone)]
pub struct SignaturePublicKey(Vec<u8>);
impl SignaturePublicKey {
pub fn new(bytes: Vec<u8>) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
impl From<Vec<u8>> for SignaturePublicKey {
fn from(bytes: Vec<u8>) -> Self {
Self(bytes)
}
}
#[derive(ZeroizeOnDrop)]
pub struct KeyGroup {
#[zeroize(skip)]
pub encryption_public_key: EncryptionPublicKey,
pub encryption_private_key: EncryptionPrivateKey,
#[zeroize(skip)]
pub signature_public_key: SignaturePublicKey,
pub signature_private_key: SignaturePrivateKey,
}
impl KeyGroup {
pub fn new(
encryption_public_key: EncryptionPublicKey,
encryption_private_key: EncryptionPrivateKey,
signature_public_key: SignaturePublicKey,
signature_private_key: SignaturePrivateKey,
) -> Self {
Self {
encryption_public_key,
encryption_private_key,
signature_public_key,
signature_private_key,
}
}
}
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct KemPrivateKey(Vec<u8>);
impl KemPrivateKey {
pub fn new(bytes: Vec<u8>) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
impl From<Vec<u8>> for KemPrivateKey {
fn from(bytes: Vec<u8>) -> Self {
Self(bytes)
}
}
#[derive(Clone)]
pub struct KemPublicKey(Vec<u8>);
impl KemPublicKey {
pub fn new(bytes: Vec<u8>) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
impl From<Vec<u8>> for KemPublicKey {
fn from(bytes: Vec<u8>) -> Self {
Self(bytes)
}
}
#[derive(Clone)]
pub struct SignaturePqPublicKey(Vec<u8>);
impl SignaturePqPublicKey {
pub fn new(bytes: Vec<u8>) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
impl From<Vec<u8>> for SignaturePqPublicKey {
fn from(bytes: Vec<u8>) -> Self {
Self(bytes)
}
}
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct SignaturePqPrivateKey(Vec<u8>);
impl SignaturePqPrivateKey {
pub fn new(bytes: Vec<u8>) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
impl From<Vec<u8>> for SignaturePqPrivateKey {
fn from(bytes: Vec<u8>) -> Self {
Self(bytes)
}
}
#[derive(ZeroizeOnDrop)]
pub struct Keyring {
#[zeroize(skip)]
pub kem_public_key: KemPublicKey,
pub kem_secret_key: KemPrivateKey,
#[zeroize(skip)]
pub sig_pq_public_key: SignaturePqPublicKey,
pub sig_pq_secret_key: SignaturePqPrivateKey,
#[zeroize(skip)]
pub sig_cl_public_key: SignaturePublicKey,
pub sig_cl_secret_key: SignaturePrivateKey,
}
impl Keyring {
pub fn new(
kem_public_key: KemPublicKey,
kem_secret_key: KemPrivateKey,
sig_pq_public_key: SignaturePqPublicKey,
sig_pq_secret_key: SignaturePqPrivateKey,
sig_cl_public_key: SignaturePublicKey,
sig_cl_secret_key: SignaturePrivateKey,
) -> Self {
Self {
kem_public_key,
kem_secret_key,
sig_pq_public_key,
sig_pq_secret_key,
sig_cl_public_key,
sig_cl_secret_key,
}
}
}

View file

@ -1,14 +1,46 @@
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
pub mod aead;
pub mod error;
pub mod keypair;
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "sha2")]
pub mod hash;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
#[cfg(feature = "hkdf")]
pub mod kdf;
#[cfg(any(feature = "ed25519-dalek", feature = "ml-dsa"))]
pub mod sign;
#[cfg(feature = "mlkem-tls")]
pub mod kem;
pub use aead::{AeadCipher, AeadDecrypt, AeadEncrypt};
pub use error::CryptoError;
pub use keypair::{
EncryptionPrivateKey, EncryptionPublicKey, KemPrivateKey, KemPublicKey, KeyGroup, Keyring,
SignaturePqPrivateKey, SignaturePqPublicKey, SignaturePrivateKey, SignaturePublicKey,
};
#[cfg(feature = "chacha20poly1305")]
pub use aead::ChaCha20Poly1305;
#[cfg(feature = "aes-gcm")]
pub use aead::Aes256Gcm;
#[cfg(feature = "ed25519-dalek")]
pub use sign::{verify_ed25519, Ed25519Signer, SignatureScheme};
#[cfg(feature = "ml-dsa")]
pub use sign::{verify_ml_dsa, MlDsaSigner};
#[cfg(all(feature = "ed25519-dalek", feature = "ml-dsa"))]
pub use sign::{sign_dual, DualSignature};
#[cfg(feature = "sha2")]
pub use hash::{sha256, sha256_double, Sha256Hasher};
#[cfg(feature = "hkdf")]
pub use kdf::{derive_encryption_key, hkdf_expand, hkdf_extract};
#[cfg(feature = "mlkem-tls")]
pub use kem::HybridKem;

232
crypto/src/sign.rs Normal file
View file

@ -0,0 +1,232 @@
use crate::error::CryptoError;
#[cfg(feature = "ed25519-dalek")]
use crate::keypair::{SignaturePrivateKey, SignaturePublicKey};
#[cfg(feature = "ed25519-dalek")]
use rand_core::RngCore;
#[cfg(feature = "ml-dsa")]
use crate::keypair::{SignaturePqPrivateKey, SignaturePqPublicKey};
pub trait SignatureScheme {
fn sign(&self, msg: &[u8]) -> Result<Vec<u8>, CryptoError>;
fn verify(&self, msg: &[u8], signature: &[u8]) -> Result<(), CryptoError>;
}
#[cfg(feature = "ed25519-dalek")]
pub struct Ed25519Signer {
secret: ed25519_dalek::SigningKey,
public: ed25519_dalek::VerifyingKey,
}
#[cfg(feature = "ed25519-dalek")]
impl Ed25519Signer {
pub fn new(secret_key: &SignaturePrivateKey) -> Result<Self, CryptoError> {
let bytes: [u8; 32] = secret_key
.as_bytes()
.try_into()
.map_err(|_| CryptoError::KeyGenerationFailed)?;
let secret = ed25519_dalek::SigningKey::from_bytes(&bytes);
let public = secret.verifying_key();
Ok(Self { secret, public })
}
pub fn generate() -> (Self, SignaturePrivateKey, SignaturePublicKey) {
let mut bytes = [0u8; 32];
rand_core::OsRng.fill_bytes(&mut bytes);
let secret = ed25519_dalek::SigningKey::from_bytes(&bytes);
let public = secret.verifying_key();
let priv_key = SignaturePrivateKey::new(secret.to_bytes().to_vec());
let pub_key = SignaturePublicKey::new(public.to_bytes().to_vec());
let signer = Self { secret, public };
(signer, priv_key, pub_key)
}
pub fn public_key(&self) -> SignaturePublicKey {
SignaturePublicKey::new(self.public.to_bytes().to_vec())
}
}
#[cfg(feature = "ed25519-dalek")]
impl SignatureScheme for Ed25519Signer {
fn sign(&self, msg: &[u8]) -> Result<Vec<u8>, CryptoError> {
use ed25519_dalek::Signer;
let signature = self.secret.sign(msg).to_bytes().to_vec();
Ok(signature)
}
fn verify(&self, msg: &[u8], signature: &[u8]) -> Result<(), CryptoError> {
use ed25519_dalek::Verifier;
let sig_bytes: [u8; 64] = signature
.try_into()
.map_err(|_| CryptoError::InvalidSignature)?;
let sig = ed25519_dalek::Signature::from_bytes(&sig_bytes);
self.public
.verify(msg, &sig)
.map_err(|_| CryptoError::VerificationFailed)
}
}
#[cfg(feature = "ed25519-dalek")]
pub fn verify_ed25519(
public_key: &SignaturePublicKey,
msg: &[u8],
signature: &[u8],
) -> Result<(), CryptoError> {
use ed25519_dalek::Verifier;
let pub_bytes: [u8; 32] = public_key
.as_bytes()
.try_into()
.map_err(|_| CryptoError::InvalidSignature)?;
let public = ed25519_dalek::VerifyingKey::from_bytes(&pub_bytes)
.map_err(|_| CryptoError::InvalidSignature)?;
let sig_bytes: [u8; 64] = signature
.try_into()
.map_err(|_| CryptoError::InvalidSignature)?;
let sig = ed25519_dalek::Signature::from_bytes(&sig_bytes);
public
.verify(msg, &sig)
.map_err(|_| CryptoError::VerificationFailed)
}
#[cfg(feature = "ml-dsa")]
pub struct MlDsaSigner {
secret: ml_dsa::SigningKey<ml_dsa::MlDsa65>,
public: ml_dsa::VerifyingKey<ml_dsa::MlDsa65>,
}
#[cfg(feature = "ml-dsa")]
impl MlDsaSigner {
pub fn new(
secret_key: &SignaturePqPrivateKey,
public_key: &SignaturePqPublicKey,
) -> Result<Self, CryptoError> {
let encoded_sk =
ml_dsa::EncodedSigningKey::<ml_dsa::MlDsa65>::try_from(secret_key.as_bytes())
.map_err(|_| CryptoError::KeyGenerationFailed)?;
let secret = ml_dsa::SigningKey::<ml_dsa::MlDsa65>::decode(&encoded_sk);
let encoded_pk =
ml_dsa::EncodedVerifyingKey::<ml_dsa::MlDsa65>::try_from(public_key.as_bytes())
.map_err(|_| CryptoError::KeyGenerationFailed)?;
let public = ml_dsa::VerifyingKey::<ml_dsa::MlDsa65>::decode(&encoded_pk);
Ok(Self { secret, public })
}
pub fn generate() -> (Self, SignaturePqPrivateKey, SignaturePqPublicKey) {
use ml_dsa::KeyGen;
let kp = ml_dsa::MlDsa65::key_gen(&mut rand_core::OsRng);
let secret = kp.signing_key().clone();
let public = kp.verifying_key().clone();
let priv_key = SignaturePqPrivateKey::new(secret.encode().to_vec());
let pub_key = SignaturePqPublicKey::new(public.encode().to_vec());
let signer = Self { secret, public };
(signer, priv_key, pub_key)
}
pub fn public_key(&self) -> SignaturePqPublicKey {
SignaturePqPublicKey::new(self.public.encode().to_vec())
}
pub fn verifying_key(&self) -> &ml_dsa::VerifyingKey<ml_dsa::MlDsa65> {
&self.public
}
pub fn signing_key(&self) -> &ml_dsa::SigningKey<ml_dsa::MlDsa65> {
&self.secret
}
}
#[cfg(feature = "ml-dsa")]
impl SignatureScheme for MlDsaSigner {
fn sign(&self, msg: &[u8]) -> Result<Vec<u8>, CryptoError> {
use ml_dsa::signature::Signer;
let signature = self.secret.sign(msg);
Ok(signature.encode().to_vec())
}
fn verify(&self, msg: &[u8], signature: &[u8]) -> Result<(), CryptoError> {
use ml_dsa::signature::Verifier;
let encoded_sig =
ml_dsa::EncodedSignature::<ml_dsa::MlDsa65>::try_from(signature)
.map_err(|_| CryptoError::InvalidSignature)?;
let sig = ml_dsa::Signature::<ml_dsa::MlDsa65>::decode(&encoded_sig)
.ok_or(CryptoError::InvalidSignature)?;
self.public
.verify(msg, &sig)
.map_err(|_| CryptoError::VerificationFailed)
}
}
#[cfg(feature = "ml-dsa")]
pub fn verify_ml_dsa(
public_key: &SignaturePqPublicKey,
msg: &[u8],
signature: &[u8],
) -> Result<(), CryptoError> {
use ml_dsa::signature::Verifier;
let encoded_pk =
ml_dsa::EncodedVerifyingKey::<ml_dsa::MlDsa65>::try_from(public_key.as_bytes())
.map_err(|_| CryptoError::InvalidSignature)?;
let public = ml_dsa::VerifyingKey::<ml_dsa::MlDsa65>::decode(&encoded_pk);
let encoded_sig =
ml_dsa::EncodedSignature::<ml_dsa::MlDsa65>::try_from(signature)
.map_err(|_| CryptoError::InvalidSignature)?;
let sig = ml_dsa::Signature::<ml_dsa::MlDsa65>::decode(&encoded_sig)
.ok_or(CryptoError::InvalidSignature)?;
public
.verify(msg, &sig)
.map_err(|_| CryptoError::VerificationFailed)
}
pub struct DualSignature {
pub ed25519: Vec<u8>,
pub mldsa: Vec<u8>,
}
#[cfg(all(feature = "ed25519-dalek", feature = "ml-dsa"))]
pub fn sign_dual(
ed25519_sk: &ed25519_dalek::SigningKey,
mldsa_sk: &ml_dsa::SigningKey<ml_dsa::MlDsa65>,
message: &[u8],
) -> DualSignature {
use ed25519_dalek::Signer;
DualSignature {
ed25519: ed25519_sk.sign(message).to_bytes().to_vec(),
mldsa: mldsa_sk.sign(message).encode().to_vec(),
}
}
impl DualSignature {
#[cfg(all(feature = "ed25519-dalek", feature = "ml-dsa"))]
pub fn verify(
&self,
ed25519_vk: &ed25519_dalek::VerifyingKey,
mldsa_vk: &ml_dsa::VerifyingKey<ml_dsa::MlDsa65>,
message: &[u8],
) -> Result<(), CryptoError> {
use ed25519_dalek::Verifier;
let ed_sig = ed25519_dalek::Signature::from_slice(&self.ed25519)
.map_err(|_| CryptoError::InvalidSignature)?;
ed25519_vk
.verify(message, &ed_sig)
.map_err(|_| CryptoError::VerificationFailed)?;
let encoded_sig =
ml_dsa::EncodedSignature::<ml_dsa::MlDsa65>::try_from(self.mldsa.as_slice())
.map_err(|_| CryptoError::InvalidSignature)?;
let ml_sig = ml_dsa::Signature::<ml_dsa::MlDsa65>::decode(&encoded_sig)
.ok_or(CryptoError::InvalidSignature)?;
mldsa_vk
.verify(message, &ml_sig)
.map_err(|_| CryptoError::VerificationFailed)?;
Ok(())
}
}