|
|
@@ -0,0 +1,197 @@
|
|
|
+/*! A module for the protocol for a user to request the issuing of an
|
|
|
+Invitation credential they can pass to someone they know.
|
|
|
+
|
|
|
+They are allowed to do this as long as their current Lox credentials has
|
|
|
+a non-zero "invites_remaining" attribute (which will be decreased by
|
|
|
+one), and they have a Bucket Reachability credential for their current
|
|
|
+bucket and today's date. (Such credentials are placed daily in the
|
|
|
+encrypted bridge table.)
|
|
|
+
|
|
|
+The user presents their current Lox credential:
|
|
|
+- id: revealed
|
|
|
+- bucket: blinded
|
|
|
+- trust_level: blinded
|
|
|
+- level_since: blinded
|
|
|
+- invites_remaining: blinded, but proved in ZK that it's not zero
|
|
|
+- blockages: blinded
|
|
|
+
|
|
|
+and a Bucket Reachability credential:
|
|
|
+- date: revealed to be today
|
|
|
+- bucket: blinded, but proved in ZK that it's the same as in the Lox
|
|
|
+ credential above
|
|
|
+
|
|
|
+and a new Lox credential to be issued:
|
|
|
+
|
|
|
+- id: jointly chosen by the user and BA
|
|
|
+- bucket: blinded, but proved in ZK that it's the same as in the Lox
|
|
|
+ credential above
|
|
|
+- trust_level: blinded, but proved in ZK that it's the same as in the
|
|
|
+ Lox credential above
|
|
|
+- level_since: blinded, but proved in ZK that it's the same as in the
|
|
|
+ Lox credential above
|
|
|
+- invites_remaining: blinded, but proved in ZK that it's one less than
|
|
|
+ the number in the Lox credential above
|
|
|
+- blockages: blinded, but proved in ZK that it's the same as in the
|
|
|
+ Lox credential above
|
|
|
+
|
|
|
+and a new Invitation credential to be issued:
|
|
|
+
|
|
|
+- inv_id: jointly chosen by the user and BA
|
|
|
+- date: revealed to be today
|
|
|
+- bucket: blinded, but proved in ZK that it's the same as in the Lox
|
|
|
+ credential above
|
|
|
+- blockages: blinded, but proved in ZK that it's the same as in the Lox
|
|
|
+ credential above
|
|
|
+
|
|
|
+*/
|
|
|
+
|
|
|
+#[cfg(feature = "bridgeauth")]
|
|
|
+use super::super::dup_filter::SeenType;
|
|
|
+use super::super::scalar_u32;
|
|
|
+#[cfg(feature = "bridgeauth")]
|
|
|
+use super::super::BridgeAuth;
|
|
|
+use super::errors::CredentialError;
|
|
|
+use crate::lox_creds::{BucketReachability, Invitation, Lox};
|
|
|
+use cmz::*;
|
|
|
+use curve25519_dalek::ristretto::RistrettoPoint as G;
|
|
|
+use curve25519_dalek::scalar::Scalar;
|
|
|
+use group::Group;
|
|
|
+use rand_core::RngCore;
|
|
|
+use sha2::Sha512;
|
|
|
+
|
|
|
+/// Invitations must be used within this many days of being issued.
|
|
|
+/// Note that if you change this number to be larger than 15, you must
|
|
|
+/// also add bits to the zero knowledge proof.
|
|
|
+pub const INVITATION_EXPIRY: u32 = 15;
|
|
|
+
|
|
|
+muCMZProtocol! { issue_invite,
|
|
|
+ [L: Lox {id: R, bucket: H, trust_level: H, level_since: H, invites_remaining: H, blockages: H}, B: BucketReachability { date: R, bucket: H } ],
|
|
|
+ [ I: Invitation { inv_id: J, date: S, bucket: H, blockages: H }, N: Lox {id: J, bucket: H, trust_level: H, level_since: H, invites_remaining: H, blockages: H }],
|
|
|
+ L.bucket = B.bucket,
|
|
|
+ //L.invites_remaining > 0,
|
|
|
+ N.bucket = L.bucket,
|
|
|
+ N.trust_level = L.trust_level,
|
|
|
+ N.level_since = L.level_since,
|
|
|
+ N.invites_remaining = L.invites_remaining - Scalar::ONE,
|
|
|
+ N.blockages = L.blockages,
|
|
|
+ I. bucket = L.bucket,
|
|
|
+ I.blockages = L.blockages
|
|
|
+}
|
|
|
+
|
|
|
+pub fn request(
|
|
|
+ L: Lox,
|
|
|
+ lox_pubkeys: CMZPubkey<G>,
|
|
|
+ B: BucketReachability,
|
|
|
+ inv_pub: CMZPubkey<G>,
|
|
|
+ today: u32,
|
|
|
+) -> Result<(issue_invite::Request, issue_invite::ClientState), CredentialError> {
|
|
|
+ let mut rng = rand::thread_rng();
|
|
|
+ cmz_group_init(G::hash_from_bytes::<Sha512>(b"CMZ Generator A"));
|
|
|
+
|
|
|
+ // Ensure the credential can be correctly shown: it must be the case
|
|
|
+ // that invites_remaining not be 0
|
|
|
+ if let Some(invites_remaining) = L.invites_remaining {
|
|
|
+ if invites_remaining == Scalar::ZERO {
|
|
|
+ return Err(CredentialError::NoInvitationsRemaining);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ return Err(CredentialError::InvalidField(
|
|
|
+ String::from("invites_remaining"),
|
|
|
+ String::from("None"),
|
|
|
+ ));
|
|
|
+ }
|
|
|
+
|
|
|
+ // The buckets in the Lox and Bucket Reachability credentials have
|
|
|
+ // to match
|
|
|
+ if L.bucket.is_some_and(|b| b != B.bucket.unwrap()) {
|
|
|
+ return Err(CredentialError::CredentialMismatch);
|
|
|
+ }
|
|
|
+ // The Bucket Reachability credential has to be dated today
|
|
|
+ let reach_date: u32 = match scalar_u32(&B.date.unwrap()) {
|
|
|
+ Some(v) => v,
|
|
|
+ None => {
|
|
|
+ return Err(CredentialError::InvalidField(
|
|
|
+ String::from("date"),
|
|
|
+ String::from("could not be converted to u32"),
|
|
|
+ ))
|
|
|
+ }
|
|
|
+ };
|
|
|
+ if reach_date != today {
|
|
|
+ return Err(CredentialError::InvalidField(
|
|
|
+ String::from("date"),
|
|
|
+ String::from("reachability credential must be generated today"),
|
|
|
+ ));
|
|
|
+ }
|
|
|
+
|
|
|
+ //TODO check all values are not None
|
|
|
+ let mut I = Invitation::using_pubkey(&inv_pub);
|
|
|
+ let mut N = Lox::using_pubkey(&lox_pubkeys);
|
|
|
+ N.bucket = L.bucket;
|
|
|
+ N.trust_level = L.trust_level;
|
|
|
+ N.level_since = L.level_since;
|
|
|
+ N.invites_remaining = Some(L.invites_remaining.unwrap() - Scalar::ONE);
|
|
|
+ N.blockages = L.blockages;
|
|
|
+ I.bucket = L.bucket;
|
|
|
+ I.blockages = L.blockages;
|
|
|
+
|
|
|
+ match issue_invite::prepare(&mut rng, &L, &B, I, N) {
|
|
|
+ Ok(req_state) => Ok(req_state),
|
|
|
+ Err(e) => Err(CredentialError::CMZError(e)),
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg(feature = "bridgeauth")]
|
|
|
+impl BridgeAuth {
|
|
|
+ pub fn handle_issue_invite(
|
|
|
+ &mut self,
|
|
|
+ req: issue_invite::Request,
|
|
|
+ ) -> Result<issue_invite::Reply, CredentialError> {
|
|
|
+ let mut rng = rand::thread_rng();
|
|
|
+ let reqbytes = req.as_bytes();
|
|
|
+ let recvreq = issue_invite::Request::try_from(&reqbytes[..]).unwrap();
|
|
|
+ let today = self.today();
|
|
|
+ match issue_invite::handle(
|
|
|
+ &mut rng,
|
|
|
+ recvreq,
|
|
|
+ |L: &mut Lox, B: &mut BucketReachability, I: &mut Invitation, N: &mut Lox| {
|
|
|
+ L.set_privkey(&self.lox_priv);
|
|
|
+ B.set_privkey(&self.reachability_priv);
|
|
|
+ I.set_privkey(&self.invitation_priv);
|
|
|
+ N.set_privkey(&self.lox_priv);
|
|
|
+ if B.date.is_some_and(|b| b != today.into()) {
|
|
|
+ return Err(CMZError::RevealAttrMissing("date", "not today"));
|
|
|
+ }
|
|
|
+ L.bucket = B.bucket;
|
|
|
+ N.bucket = L.bucket;
|
|
|
+ N.trust_level = L.trust_level;
|
|
|
+ N.level_since = Some(today.into());
|
|
|
+ N.invites_remaining = Some(L.invites_remaining.unwrap() - Scalar::ONE);
|
|
|
+ N.blockages = L.blockages;
|
|
|
+ I.bucket = N.bucket;
|
|
|
+ I.blockages = L.blockages;
|
|
|
+ Ok(())
|
|
|
+ },
|
|
|
+ |_L: &Lox, _B: &BucketReachability, I: &Invitation, _N: &Lox| {
|
|
|
+ if self.inv_id_filter.filter(&I.inv_id.unwrap()) == SeenType::Seen {
|
|
|
+ return Err(CMZError::RevealAttrMissing("id", "Credential Expired"));
|
|
|
+ }
|
|
|
+ Ok(())
|
|
|
+ },
|
|
|
+ ) {
|
|
|
+ Ok((response, (_L_issuer, _B_issuer, _I_issuer, _N_issuer))) => Ok(response),
|
|
|
+ Err(e) => Err(CredentialError::CMZError(e)),
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+pub fn handle_response(
|
|
|
+ state: issue_invite::ClientState,
|
|
|
+ rep: issue_invite::Reply,
|
|
|
+) -> Result<(Invitation, Lox), CMZError> {
|
|
|
+ let replybytes = rep.as_bytes();
|
|
|
+ let recvreply = issue_invite::Reply::try_from(&replybytes[..]).unwrap();
|
|
|
+ match state.finalize(recvreply) {
|
|
|
+ Ok(creds) => Ok(creds),
|
|
|
+ Err(_e) => Err(CMZError::Unknown),
|
|
|
+ }
|
|
|
+}
|