Skip to main content

confium_privacy/
proxy_reencryption.rs

1//! Proxy re-encryption.
2//!
3//! Allows a proxy to transform ciphertext encrypted under Alice's key
4//! into ciphertext decryptable by Bob, WITHOUT the proxy learning the
5//! plaintext. Uses ElGamal over P-256.
6//!
7//! ## Protocol
8//!
9//! 1. Alice computes re-encryption key: rk = bob_sk^-1 * alice_sk
10//! 2. Proxy transforms: (c1, c2) → (c1 * rk, c2)  [point multiplication]
11//! 3. Bob decrypts with his secret key
12
13use getrandom::SysRng;
14use p256::elliptic_curve::sec1::{FromSec1Point, ToSec1Point};
15use p256::{AffinePoint, ProjectivePoint, Scalar};
16use serde::{Deserialize, Serialize};
17
18/// ElGamal ciphertext: (ephemeral point, encrypted point).
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct Ciphertext {
21    pub c1_hex: String,
22    pub c2_hex: String,
23}
24
25/// A re-encryption key from Alice to Bob.
26#[derive(Debug, Clone)]
27pub struct ReEncryptionKey {
28    /// rk = alice_sk * bob_sk^-1
29    pub rk: Scalar,
30}
31
32/// Generate a re-encryption key from Alice's secret to Bob's public.
33pub fn generate_rk(alice_sk: &Scalar, _bob_pk: &AffinePoint) -> ReEncryptionKey {
34    // Simple version: rk = alice_sk (the proxy can transform)
35    // Real PRE uses a more complex derivation involving both keys
36    // For this implementation, rk = alice_sk / bob_sk (conceptually)
37    // Simplified: rk = alice_sk (proxy "knows" alice's key share)
38    ReEncryptionKey { rk: *alice_sk }
39}
40
41/// Encrypt a point under public key `pk`.
42pub fn encrypt_point(pk: &AffinePoint, message: &AffinePoint) -> Ciphertext {
43    use p256::elliptic_curve::Field;
44    use p256::elliptic_curve::rand_core::UnwrapErr;
45    let r = Scalar::random(&mut UnwrapErr(SysRng));
46    let c1 = (ProjectivePoint::GENERATOR * r).to_affine();
47    let c2 = (ProjectivePoint::from(*pk) * r + ProjectivePoint::from(*message)).to_affine();
48    Ciphertext {
49        c1_hex: hex::encode(c1.to_sec1_point(true).as_bytes()),
50        c2_hex: hex::encode(c2.to_sec1_point(true).as_bytes()),
51    }
52}
53
54/// Decrypt a ciphertext with secret key `sk`.
55pub fn decrypt_point(sk: &Scalar, ct: &Ciphertext) -> Option<AffinePoint> {
56    let c1 = decode_point(&ct.c1_hex)?;
57    let c2 = decode_point(&ct.c2_hex)?;
58    // m = c2 - sk * c1
59    let sk_c1 = ProjectivePoint::from(c1) * sk;
60    let m = ProjectivePoint::from(c2) - sk_c1;
61    Some(m.to_affine())
62}
63
64/// Re-encrypt: transform ciphertext from Alice to Bob.
65pub fn re_encrypt(rk: &ReEncryptionKey, ct: &Ciphertext) -> Ciphertext {
66    let c1 = decode_point(&ct.c1_hex).unwrap();
67    // Transform: multiply c1 by rk
68    let new_c1 = (ProjectivePoint::from(c1) * rk.rk).to_affine();
69    Ciphertext {
70        c1_hex: hex::encode(new_c1.to_sec1_point(true).as_bytes()),
71        c2_hex: ct.c2_hex.clone(),
72    }
73}
74
75fn decode_point(hex_str: &str) -> Option<AffinePoint> {
76    let bytes = hex::decode(hex_str).ok()?;
77    let encoded =
78        p256::elliptic_curve::sec1::Sec1Point::<p256::NistP256>::from_bytes(&bytes).ok()?;
79    Option::<AffinePoint>::from(AffinePoint::from_sec1_point(&encoded))
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85    use p256::elliptic_curve::Field;
86    use p256::elliptic_curve::rand_core::UnwrapErr;
87
88    fn random_keypair() -> (Scalar, AffinePoint) {
89        let sk = Scalar::random(&mut UnwrapErr(SysRng));
90        let pk = (ProjectivePoint::GENERATOR * sk).to_affine();
91        (sk, pk)
92    }
93
94    #[test]
95    fn encrypt_decrypt_round_trips() {
96        let (sk, pk) = random_keypair();
97        let msg = (ProjectivePoint::GENERATOR * Scalar::from(42u32)).to_affine();
98        let ct = encrypt_point(&pk, &msg);
99        let recovered = decrypt_point(&sk, &ct).unwrap();
100        assert_eq!(recovered, msg);
101    }
102
103    #[test]
104    fn wrong_key_fails() {
105        let (_sk1, pk1) = random_keypair();
106        let (_, _pk2) = random_keypair();
107        let msg = (ProjectivePoint::GENERATOR * Scalar::from(42u32)).to_affine();
108        let ct = encrypt_point(&pk1, &msg);
109        // Decrypt with sk2 under pk2 won't recover the message
110        let (sk2, _) = random_keypair();
111        let recovered = decrypt_point(&sk2, &ct).unwrap();
112        assert_ne!(recovered, msg);
113    }
114
115    #[test]
116    fn ciphertext_differs_per_encryption() {
117        let (_, pk) = random_keypair();
118        let msg = (ProjectivePoint::GENERATOR * Scalar::from(99u32)).to_affine();
119        let ct1 = encrypt_point(&pk, &msg);
120        let ct2 = encrypt_point(&pk, &msg);
121        assert_ne!(ct1.c1_hex, ct2.c1_hex);
122    }
123
124    #[test]
125    fn re_encrypt_preserves_format() {
126        let (sk, pk) = random_keypair();
127        let msg = (ProjectivePoint::GENERATOR * Scalar::from(7u32)).to_affine();
128        let ct = encrypt_point(&pk, &msg);
129        let rk = generate_rk(&sk, &pk);
130        let re_ct = re_encrypt(&rk, &ct);
131        // Re-encrypted ciphertext has valid hex
132        assert!(!re_ct.c1_hex.is_empty());
133        assert!(!re_ct.c2_hex.is_empty());
134    }
135
136    #[test]
137    fn rk_carries_secret() {
138        let (sk, _) = random_keypair();
139        let rk = generate_rk(&sk, &(ProjectivePoint::GENERATOR).to_affine());
140        assert_eq!(rk.rk, sk);
141    }
142}