Skip to main content

confium_tc/kem/
encapsulate.rs

1//! Encapsulation (single-party — anyone can encrypt).
2//!
3//! Threshold KEM encapsulation is identical to single-party KEM
4//! encapsulation: generate an ephemeral keypair, derive the shared
5//! secret, encrypt the shared secret to the recipient's public key.
6//! The difference is in decapsulation: T-of-N parties must collaborate.
7
8use crate::kem::share::ThresholdShare;
9use serde::{Deserialize, Serialize};
10
11/// The recipient's threshold public key (algorithm-agnostic bytes).
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct ThresholdPublicKey {
14    /// Algorithm identifier (e.g., "ElGamal-P256-threshold", "ML-KEM-768-threshold").
15    pub algorithm: String,
16    /// Raw key bytes (format depends on algorithm).
17    pub bytes: Vec<u8>,
18}
19
20/// An encapsulated key — produced by encapsulate, consumed by threshold decapsulate.
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct EncapsulatedKey {
23    /// Algorithm identifier.
24    pub algorithm: String,
25    /// Encapsulated key bytes (the "ciphertext" of the KEM).
26    pub bytes: Vec<u8>,
27}
28
29/// The shared secret derived during encapsulation. The encryptor uses
30/// this as the AEAD key to encrypt the actual plaintext.
31#[derive(Debug, Clone, Serialize, Deserialize, zeroize::ZeroizeOnDrop)]
32pub struct SharedSecret {
33    /// Raw shared secret bytes.
34    pub bytes: Vec<u8>,
35}
36
37/// Errors during encapsulation.
38#[derive(Debug, thiserror::Error)]
39pub enum EncapsulateError {
40    /// Unknown algorithm.
41    #[error("unknown algorithm: {0}")]
42    UnknownAlgorithm(String),
43    /// Invalid public key.
44    #[error("invalid public key: {0}")]
45    InvalidPublicKey(String),
46    /// Backend failure.
47    #[error("backend failure: {0}")]
48    Backend(String),
49}
50
51/// Trait for algorithm implementations of threshold KEM encapsulation.
52pub trait Encapsulator {
53    /// Encapsulate a fresh shared secret to `recipient_public_key`.
54    /// Returns `(encapsulated_key, shared_secret)` — the encryptor keeps
55    /// the shared secret for AEAD encryption, the encapsulated key
56    /// travels with the ciphertext.
57    fn encapsulate(
58        &self,
59        recipient_public_key: &ThresholdPublicKey,
60    ) -> Result<(EncapsulatedKey, SharedSecret), EncapsulateError>;
61}
62
63/// In-memory test encapsulator for "mock" algorithm.
64///
65/// NOT FOR PRODUCTION USE. Generates a random 32-byte shared secret
66/// and stores it in the EncapsulatedKey for the decapsulator to recover.
67/// Used for testing the session lifecycle without a real crypto backend.
68pub struct MockEncapsulator;
69
70impl Encapsulator for MockEncapsulator {
71    fn encapsulate(
72        &self,
73        _recipient_public_key: &ThresholdPublicKey,
74    ) -> Result<(EncapsulatedKey, SharedSecret), EncapsulateError> {
75        // Deterministic mock: shared secret is 32 zero bytes.
76        // EncapsulatedKey carries the algorithm so decapsulator knows what to do.
77        Ok((
78            EncapsulatedKey {
79                algorithm: "mock-threshold-kem".into(),
80                bytes: vec![0u8; 32],
81            },
82            SharedSecret {
83                bytes: vec![0u8; 32],
84            },
85        ))
86    }
87}
88
89/// Convenience function: encapsulate using the mock encapsulator.
90pub fn encapsulate_mock(
91    recipient: &ThresholdPublicKey,
92) -> Result<(EncapsulatedKey, SharedSecret), EncapsulateError> {
93    MockEncapsulator.encapsulate(recipient)
94}
95
96/// Marker for the decapsulator side — verifies the share is for the right algorithm.
97pub fn validate_share_for_algorithm(
98    share: &ThresholdShare,
99    algorithm: &str,
100) -> Result<(), EncapsulateError> {
101    if share.algorithm != algorithm {
102        return Err(EncapsulateError::InvalidPublicKey(format!(
103            "share algorithm {} does not match expected {}",
104            share.algorithm, algorithm
105        )));
106    }
107    Ok(())
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn mock_encapsulator_round_trip_data() {
116        let pk = ThresholdPublicKey {
117            algorithm: "mock-threshold-kem".into(),
118            bytes: vec![1u8; 32],
119        };
120        let (ek, ss) = encapsulate_mock(&pk).unwrap();
121        assert_eq!(ek.bytes.len(), 32);
122        assert_eq!(ss.bytes.len(), 32);
123    }
124
125    #[test]
126    fn validate_share_checks_algorithm() {
127        let share = ThresholdShare {
128            algorithm: "different-alg".into(),
129            party_index: 0,
130            bytes: vec![],
131        };
132        let result = validate_share_for_algorithm(&share, "expected-alg");
133        assert!(result.is_err());
134    }
135}