securedrop_protocol_minimal/primitives/
xwing.rs1use crate::primitives::provider::hpke_rs::{HpkePrivateKey, HpkePublicKey};
2use crate::primitives::provider::kem::{PrivateKey, PublicKey};
3use rand_core::{CryptoRng, RngCore};
4
5pub const XWING_PUBLIC_KEY_LEN: usize = 1216;
7pub(crate) const XWING_PRIVATE_KEY_LEN: usize = 32;
8pub(crate) const LEN_XWING_SHAREDSECRET_ENCAPS: usize = 1120;
9
10#[derive(Debug, Clone)]
12pub(crate) struct XWingPublicKey([u8; XWING_PUBLIC_KEY_LEN]);
13
14#[derive(Debug, Clone)]
16pub(crate) struct XWingPrivateKey([u8; XWING_PRIVATE_KEY_LEN]);
17
18impl XWingPublicKey {
19 pub(crate) fn as_bytes(&self) -> &[u8; XWING_PUBLIC_KEY_LEN] {
21 &self.0
22 }
23
24 pub(crate) fn from_bytes(bytes: [u8; XWING_PUBLIC_KEY_LEN]) -> Self {
26 Self(bytes)
27 }
28}
29
30impl XWingPrivateKey {
31 pub(crate) fn as_bytes(&self) -> &[u8; XWING_PRIVATE_KEY_LEN] {
33 &self.0
34 }
35
36 pub(crate) fn from_bytes(bytes: [u8; XWING_PRIVATE_KEY_LEN]) -> Self {
38 Self(bytes)
39 }
40}
41
42impl From<XWingPrivateKey> for HpkePrivateKey {
43 fn from(sk: XWingPrivateKey) -> Self {
44 HpkePrivateKey::from(sk.0.to_vec())
45 }
46}
47
48impl From<XWingPublicKey> for HpkePublicKey {
49 fn from(pk: XWingPublicKey) -> Self {
50 HpkePublicKey::from(pk.0.to_vec())
51 }
52}
53
54pub(crate) fn deterministic_keygen(
57 randomness: [u8; 32],
58) -> Result<(XWingPrivateKey, XWingPublicKey), anyhow::Error> {
59 use crate::primitives::provider::kem::{Algorithm, key_gen_derand};
60
61 let (sk, pk) = key_gen_derand(Algorithm::XWingKemDraft06, &randomness)
63 .map_err(|e| anyhow::anyhow!("XWING deterministic key generation failed: {:?}", e))?;
64
65 typed(sk, pk)
66}
67
68#[cfg_attr(hax, hax_lib::fstar::verification_status(lax))]
69fn typed(
71 sk: PrivateKey,
72 pk: PublicKey,
73) -> Result<(XWingPrivateKey, XWingPublicKey), anyhow::Error> {
74 let private_key_bytes = sk.encode();
76 let public_key_bytes = pk.encode();
77
78 if private_key_bytes.len() != XWING_PRIVATE_KEY_LEN
80 || public_key_bytes.len() != XWING_PUBLIC_KEY_LEN
81 {
82 return Err(anyhow::anyhow!(
93 "Unexpected XWING key sizes: private={}, public={}",
94 private_key_bytes.len(),
95 public_key_bytes.len()
96 ));
97 }
98
99 let private_key = XWingPrivateKey::from_bytes(
100 private_key_bytes
101 .try_into()
102 .map_err(|_| anyhow::anyhow!("Failed to convert private key bytes"))?,
103 );
104 let public_key = XWingPublicKey::from_bytes(
105 public_key_bytes
106 .try_into()
107 .map_err(|_| anyhow::anyhow!("Failed to convert public key bytes"))?,
108 );
109
110 Ok((private_key, public_key))
111}
112
113pub(crate) fn generate_xwing_keypair<R: RngCore + CryptoRng>(
115 rng: &mut R,
116) -> Result<(XWingPrivateKey, XWingPublicKey), anyhow::Error> {
117 use crate::primitives::provider::kem::{Algorithm, key_gen};
118
119 let (sk, pk) = key_gen(Algorithm::XWingKemDraft06, rng)
121 .map_err(|e| anyhow::anyhow!("XWING key generation failed: {:?}", e))?;
122
123 typed(sk, pk)
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use proptest::prelude::*;
130 use rand_chacha::ChaCha20Rng;
131 use rand_core::SeedableRng;
132
133 #[test]
134 fn test_xwing_key_generation() {
135 let mut rng = ChaCha20Rng::seed_from_u64(42);
136
137 let (private_key, public_key) =
138 generate_xwing_keypair(&mut rng).expect("Should generate XWING keypair");
139 }
140
141 #[test]
142 fn test_xwing_key_serialization() {
143 let mut rng = ChaCha20Rng::seed_from_u64(42);
144
145 let (private_key, public_key) =
146 generate_xwing_keypair(&mut rng).expect("Should generate XWING keypair");
147
148 let private_bytes = *private_key.as_bytes();
150 let public_bytes = *public_key.as_bytes();
151
152 let reconstructed_private = XWingPrivateKey::from_bytes(private_bytes);
153 let reconstructed_public = XWingPublicKey::from_bytes(public_bytes);
154
155 assert_eq!(private_key.as_bytes(), reconstructed_private.as_bytes());
156 assert_eq!(public_key.as_bytes(), reconstructed_public.as_bytes());
157 }
158
159 #[test]
160 fn test_deterministic_keygen() {
161 proptest!(|(randomness in proptest::array::uniform32(any::<u8>()).prop_filter("exclude zero", |arr| arr != &[0u8; 32]))| {
162 let (private_key, public_key) = deterministic_keygen(randomness.try_into().unwrap()).unwrap();
163 });
164 }
165}