use crate::io::{self, Output, Result};
use clap::Subcommand;
use libpep::client::distributed::{
make_session_keys_distributed, update_attribute_session_key, update_pseudonym_session_key,
};
use libpep::elgamal::arithmetic::scalars::ScalarTraits;
use libpep::factors::{make_attribute_rekey_factor, make_pseudonym_rekey_factor};
use libpep::keys::distribution::{
make_distributed_global_keys, make_session_key_shares, AttributeSessionKeyShare,
BlindedAttributeGlobalSecretKey, BlindedGlobalSecretKey, BlindedGlobalSecretKeys,
BlindedPseudonymGlobalSecretKey, BlindingFactor, PseudonymSessionKeyShare, SessionKeyShare,
SessionKeyShares,
};
#[cfg(feature = "json")]
use libpep::keys::SessionKeys;
use libpep::keys::{
make_attribute_session_keys, make_global_keys, make_pseudonym_session_keys,
AttributeGlobalSecretKey, AttributeSessionKeys, AttributeSessionSecretKey,
PseudonymGlobalSecretKey, PseudonymSessionKeys, PseudonymSessionSecretKey, PublicKey,
SecretKey,
};
use rand_core::{CryptoRng, Rng};
#[derive(Subcommand)]
pub enum Keys {
#[command(subcommand)]
Global(Global),
#[command(subcommand)]
Session(Session),
#[command(subcommand)]
Distributed(Distributed),
}
#[derive(Subcommand)]
pub enum Global {
Generate,
}
#[derive(Subcommand)]
pub enum Session {
Derive {
#[arg(long)]
pseudonym_global_secret: Option<String>,
#[arg(long)]
attribute_global_secret: Option<String>,
#[arg(long)]
secret: String,
#[arg(long)]
context: String,
},
}
#[derive(Subcommand)]
pub enum Distributed {
Setup {
#[arg(short, long)]
n: usize,
},
Share {
#[arg(long)]
blinding: String,
#[arg(long)]
secret: String,
#[arg(long)]
context: String,
},
Reconstruct {
#[arg(long)]
blinded_pseudonym: Option<String>,
#[arg(long)]
blinded_attribute: Option<String>,
#[arg(long = "pseudonym-share")]
pseudonym_shares: Vec<String>,
#[arg(long = "attribute-share")]
attribute_shares: Vec<String>,
},
Update {
#[arg(long)]
secret_key: String,
#[arg(long)]
old_share: String,
#[arg(long)]
new_share: String,
#[arg(long)]
attribute: bool,
},
}
pub fn run<R: Rng + CryptoRng>(cmd: Keys, rng: &mut R, out: &mut Output) -> Result<()> {
match cmd {
Keys::Global(Global::Generate) => {
let (public, secret) = make_global_keys(rng);
out.value("pseudonym_public_key", public.pseudonym.to_hex());
out.value("pseudonym_secret_key", secret.pseudonym.value().to_hex());
out.value("attribute_public_key", public.attribute.to_hex());
out.value("attribute_secret_key", secret.attribute.value().to_hex());
#[cfg(feature = "json")]
if out.json() {
out.value(
"global_public_keys",
serde_json::to_value(public).expect("keys are serializable"),
);
}
}
Keys::Session(Session::Derive {
pseudonym_global_secret,
attribute_global_secret,
secret,
context,
}) => {
if pseudonym_global_secret.is_none() && attribute_global_secret.is_none() {
return Err(io::Error::input(
"give --pseudonym-global-secret, --attribute-global-secret or both",
));
}
let secret = io::encryption_secret(&secret)?;
let context = io::context(Some(&context))?;
let mut pseudonym = None;
if let Some(global) = pseudonym_global_secret {
let global: PseudonymGlobalSecretKey =
io::secret_key(&global, "pseudonym global secret key")?;
let (public, secret) = make_pseudonym_session_keys(&global, &context, &secret);
out.value("pseudonym_public_key", public.to_hex());
out.value("pseudonym_secret_key", secret.value().to_hex());
pseudonym = Some(PseudonymSessionKeys { public, secret });
}
let mut attribute = None;
if let Some(global) = attribute_global_secret {
let global: AttributeGlobalSecretKey =
io::secret_key(&global, "attribute global secret key")?;
let (public, secret) = make_attribute_session_keys(&global, &context, &secret);
out.value("attribute_public_key", public.to_hex());
out.value("attribute_secret_key", secret.value().to_hex());
attribute = Some(AttributeSessionKeys { public, secret });
}
#[cfg(feature = "json")]
if let (true, Some(pseudonym), Some(attribute)) = (out.json(), pseudonym, attribute) {
out.value(
"session_keys",
serde_json::to_value(SessionKeys {
pseudonym,
attribute,
})
.expect("keys are serializable"),
);
}
#[cfg(not(feature = "json"))]
let _ = (pseudonym, attribute);
}
Keys::Distributed(Distributed::Setup { n }) => {
if n == 0 {
return Err(io::Error::input(
"a distributed system needs at least one transcryptor",
));
}
let (public, blinded, factors) = make_distributed_global_keys(n, rng);
out.value("pseudonym_public_key", public.pseudonym.to_hex());
out.value("attribute_public_key", public.attribute.to_hex());
out.value("blinded_pseudonym_secret_key", blinded.pseudonym.to_hex());
out.value("blinded_attribute_secret_key", blinded.attribute.to_hex());
out.value(
"blinding_factors",
factors.iter().map(|f| f.to_hex()).collect::<Vec<_>>(),
);
out.note("Keep each blinding factor secret and hand it to one transcryptor.");
}
Keys::Distributed(Distributed::Share {
blinding,
secret,
context,
}) => {
let blinding = BlindingFactor::from_scalar(io::scalar(&blinding, "blinding factor")?);
let secret = io::encryption_secret(&secret)?;
let context = io::context(Some(&context))?;
let shares = make_session_key_shares(
&make_pseudonym_rekey_factor(&secret, &context),
&make_attribute_rekey_factor(&secret, &context),
&blinding,
);
out.value("pseudonym_share", shares.pseudonym.to_hex());
out.value("attribute_share", shares.attribute.to_hex());
}
Keys::Distributed(Distributed::Reconstruct {
blinded_pseudonym,
blinded_attribute,
pseudonym_shares,
attribute_shares,
}) => match (blinded_pseudonym, blinded_attribute) {
(Some(bp), Some(ba)) if pseudonym_shares.len() == attribute_shares.len() => {
let blinded = BlindedGlobalSecretKeys {
pseudonym: BlindedPseudonymGlobalSecretKey::from_scalar(io::scalar(
&bp,
"blinded pseudonym secret key",
)?),
attribute: BlindedAttributeGlobalSecretKey::from_scalar(io::scalar(
&ba,
"blinded attribute secret key",
)?),
};
let shares = pseudonym_shares
.iter()
.zip(&attribute_shares)
.map(|(p, a)| {
Ok(SessionKeyShares {
pseudonym: share(p, "pseudonym share")?,
attribute: share(a, "attribute share")?,
})
})
.collect::<Result<Vec<_>>>()?;
let keys = make_session_keys_distributed(blinded, &shares);
out.value("pseudonym_public_key", keys.pseudonym.public.to_hex());
out.value(
"pseudonym_secret_key",
keys.pseudonym.secret.value().to_hex(),
);
out.value("attribute_public_key", keys.attribute.public.to_hex());
out.value(
"attribute_secret_key",
keys.attribute.secret.value().to_hex(),
);
#[cfg(feature = "json")]
if out.json() {
out.value(
"session_keys",
serde_json::to_value(keys).expect("keys are serializable"),
);
}
}
(Some(_), Some(_)) => {
return Err(io::Error::input(
"give the same number of --pseudonym-share and --attribute-share values",
))
}
(Some(bp), None) => {
let blinded = BlindedPseudonymGlobalSecretKey::from_scalar(io::scalar(
&bp,
"blinded pseudonym secret key",
)?);
let shares = pseudonym_shares
.iter()
.map(|p| share::<PseudonymSessionKeyShare>(p, "pseudonym share"))
.collect::<Result<Vec<_>>>()?;
let (public, secret) =
libpep::client::distributed::make_pseudonym_session_key(blinded, &shares);
out.value("pseudonym_public_key", public.to_hex());
out.value("pseudonym_secret_key", secret.value().to_hex());
}
(None, Some(ba)) => {
let blinded = BlindedAttributeGlobalSecretKey::from_scalar(io::scalar(
&ba,
"blinded attribute secret key",
)?);
let shares = attribute_shares
.iter()
.map(|a| share::<AttributeSessionKeyShare>(a, "attribute share"))
.collect::<Result<Vec<_>>>()?;
let (public, secret) =
libpep::client::distributed::make_attribute_session_key(blinded, &shares);
out.value("attribute_public_key", public.to_hex());
out.value("attribute_secret_key", secret.value().to_hex());
}
(None, None) => {
return Err(io::Error::input(
"give --blinded-pseudonym, --blinded-attribute or both",
))
}
},
Keys::Distributed(Distributed::Update {
secret_key,
old_share,
new_share,
attribute,
}) => {
if attribute {
let key: AttributeSessionSecretKey =
io::secret_key(&secret_key, "attribute session secret key")?;
let (public, secret) = update_attribute_session_key(
key,
share(&old_share, "old share")?,
share(&new_share, "new share")?,
);
out.value("attribute_public_key", public.to_hex());
out.value("attribute_secret_key", secret.value().to_hex());
} else {
let key: PseudonymSessionSecretKey =
io::secret_key(&secret_key, "pseudonym session secret key")?;
let (public, secret) = update_pseudonym_session_key(
key,
share(&old_share, "old share")?,
share(&new_share, "new share")?,
);
out.value("pseudonym_public_key", public.to_hex());
out.value("pseudonym_secret_key", secret.value().to_hex());
}
}
}
Ok(())
}
fn share<S: SessionKeyShare>(raw: &str, what: &str) -> Result<S> {
Ok(S::from_scalar(io::scalar(raw, what)?))
}