lox-extensions 0.1.0

A reputation-based bridge distribution system that provides privacy protection to users and their social graph while remaining open to all users.
Documentation
/*! A module for the protocol for a new user to redeem an Invitation
credential.  The user will start at trust level 1 (instead of 0 for
untrusted uninvited users).

The user presents the Invitation credential:
- id: revealed
- date: blinded, but proved in ZK to be at most INVITATION_EXPIRY days ago
- bucket: blinded
- blockages: blinded

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
  Invitation credential above
- trust_level: revealed to be 1
- level_since: today
- invites_remaining: revealed to be 0
- blockages: blinded, but proved in ZK that it's the same as in the
  Invitations credential above

*/

#[cfg(feature = "bridgeauth")]
use super::super::dup_filter::SeenType;
#[cfg(feature = "bridgeauth")]
use super::super::BridgeAuth;
use super::super::{scalar_u32, Scalar, G};
use super::errors::CredentialError;
use crate::lox_creds::{Invitation, Lox};
use crate::proto::level_up::LEVEL_INVITATIONS;
use cmz::*;
use group::Group;
use rand::{CryptoRng, RngCore};
use sha2::Sha512;

const SESSION_ID: &[u8] = b"redeem_invite";

/// 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! { redeem_invite<credential_expiry, today>,
    [ I: Invitation { inv_id: R, date: H, bucket: H, blockages: H } ],
    N: Lox {id: J, bucket: H, trust_level: I, level_since: S, invites_remaining: I, blockages: H },
    (credential_expiry..=today).contains(I.date),
    N.bucket = I.bucket,
    N.blockages = I.blockages,
}

pub fn request(
    rng: &mut (impl CryptoRng + RngCore),
    I: Invitation,
    lox_pubkeys: CMZPubkey<G>,
    today: u32,
) -> Result<(redeem_invite::Request, redeem_invite::ClientState), CredentialError> {
    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 date + INVITATION_EXPIRY >= today.
    let date: u32 = match scalar_u32(&I.date.unwrap()) {
        Some(v) => v,
        None => {
            return Err(CredentialError::InvalidField(
                String::from("date"),
                String::from("could not be converted to u32"),
            ))
        }
    };
    if date + INVITATION_EXPIRY < today {
        return Err(CredentialError::CredentialExpired);
    }
    let diffdays = date + INVITATION_EXPIRY - today;
    // If diffdays > 15, then since INVITATION_EXPIRY <= 15, then date
    // must be in the future.  Reject.
    if diffdays > 15 {
        return Err(CredentialError::InvalidField(
            String::from("date"),
            String::from("credential was created in the future"),
        ));
    }

    let params = redeem_invite::Params {
        credential_expiry: (today - INVITATION_EXPIRY).into(),
        today: today.into(),
    };

    let mut N = Lox::using_pubkey(&lox_pubkeys);
    N.bucket = I.bucket;
    N.trust_level = Some(Scalar::ONE);
    N.invites_remaining = Some(LEVEL_INVITATIONS[1].into());
    N.blockages = I.blockages;

    match redeem_invite::prepare(rng, SESSION_ID, &I, N, &params) {
        Ok(req_state) => Ok(req_state),
        Err(e) => Err(CredentialError::CMZError(e)),
    }
}

#[cfg(feature = "bridgeauth")]
impl BridgeAuth {
    pub fn handle_redeem_invite(
        &mut self,
        req: redeem_invite::Request,
    ) -> Result<redeem_invite::Reply, CredentialError> {
        let mut rng = rand::thread_rng();
        let reqbytes = req.as_bytes();
        let recvreq = redeem_invite::Request::try_from(&reqbytes[..]).unwrap();
        let today = self.today();
        match redeem_invite::handle(
            &mut rng,
            SESSION_ID,
            recvreq,
            |I: &mut Invitation, N: &mut Lox| {
                I.set_privkey(&self.invitation_priv);
                N.set_privkey(&self.lox_priv);
                let eligibility_max_age: u32 = today - INVITATION_EXPIRY;
                N.trust_level = Some(Scalar::ONE);
                N.level_since = Some(today.into());
                N.invites_remaining = Some(LEVEL_INVITATIONS[1].into());
                Ok(redeem_invite::Params {
                    credential_expiry: eligibility_max_age.into(),
                    today: today.into(),
                })
            },
            |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, (_I_isser, _N_issuer))) => Ok(response),
            Err(e) => Err(CredentialError::CMZError(e)),
        }
    }
}

pub fn handle_response(
    state: redeem_invite::ClientState,
    rep: redeem_invite::Reply,
) -> Result<Lox, CMZError> {
    let replybytes = rep.as_bytes();
    let recvreply = redeem_invite::Reply::try_from(&replybytes[..]).unwrap();
    match state.finalize(recvreply) {
        Ok(cred) => Ok(cred),
        Err(_e) => Err(CMZError::Unknown),
    }
}

#[cfg(all(test, feature = "bridgeauth"))]
mod tests {
    use crate::mock_auth::TestHarness;
    use crate::proto::redeem_invite;

    #[test]
    fn test_redeem_invite() {
        let mut th = TestHarness::new();
        let rng = &mut rand::thread_rng();
        let invite = th.bdb.invite().unwrap();
        let mut lox_cred = th.open_invite(rng, &invite);
        let mig_cred = th.trust_promotion(rng, lox_cred.clone());
        lox_cred = th.migration(rng, lox_cred.clone(), mig_cred.clone());
        lox_cred = th.level_up(rng, lox_cred.clone());
        let issue_invite_cred = th.issue_invite(rng, lox_cred.clone());
        let redeem_invite_request = redeem_invite::request(
            rng,
            issue_invite_cred.clone().0,
            th.ba.lox_pub.clone(),
            th.ba.today(),
        );
        let (redeem_invite_request, redeem_invite_client_state) = redeem_invite_request.unwrap();
        let redeem_invite_response = th.ba.handle_redeem_invite(redeem_invite_request);
        assert!(
            redeem_invite_response.is_ok(),
            "Redeem Invite response from server should succeed"
        );
        let response = redeem_invite_response.unwrap();
        let r_cred = redeem_invite::handle_response(redeem_invite_client_state, response);
        assert!(r_cred.is_ok(), "Handle response should succeed");
        th.verify_lox(&r_cred.unwrap());
    }
}