/*! A module to allow a user to prove they have trust level 3 or greater and a particular bucket. This is used by Troll Patrol for positive reports (indicating that a bridge has been accessed). The user presents their current Lox credential: - id: blinded - bucket: blinded (but proven to be bucket in provided bucket*H) - trust_level: blinded (but proven to be 3 or higher) - level_since: blinded - invites_remaining: blinded - blockages: blinded This protocol does not expose the credential's ID and does not issue a new Lox credential. The user does not receive a response. Due to the way the level proof is implemented, this protocol has hardcoded assumptions that the level is 3-4. */ use curve25519_dalek::ristretto::{RistrettoBasepointTable, RistrettoPoint}; use curve25519_dalek::scalar::Scalar; use curve25519_dalek::traits::IsIdentity; use lox_zkp::{CompactProof, ProofError, Transcript}; use serde::{Deserialize, Serialize}; use sha2::Sha512; use std::convert::TryInto; use super::super::cred; use super::super::scalar_u32; use super::super::{BridgeAuth, IssuerPubKey}; use super::super::{CMZ_A, CMZ_A_TABLE}; pub fn compute_H(date: u32) -> RistrettoPoint { RistrettoPoint::hash_from_bytes::(format!("PR Generator H for {}", date).as_bytes()) } #[derive(Serialize, Deserialize)] pub struct Request { // Fields for blind showing the Lox credential P: RistrettoPoint, CId: RistrettoPoint, CBucket: RistrettoPoint, CLevel: RistrettoPoint, CSince: RistrettoPoint, CInvRemain: RistrettoPoint, CBlockages: RistrettoPoint, CQ: RistrettoPoint, // Fields for proving which bucket we have pub date: u32, // date is used to compute H pub BP: RistrettoPoint, // Fields for proving 3 <= trust_level <= 4 // CG can be computed by verifier CGsq: RistrettoPoint, // The combined lox_zkp piUser: CompactProof, } define_proof! { requestproof, "Positive Report Request", (id, bucket, level, since, invremain, blockages, zid, zbucket, zlevel, zsince, zinvremain, zblockages, negzQ, g, zg, wg, yg), (P, CId, CBucket, CLevel, CSince, CInvRemain, CBlockages, V, Xid, Xbucket, Xlevel, Xsince, Xinvremain, Xblockages, H, BP, CG, CGsq), (A): // Blind showing of the Lox credential CId = (id*P + zid*A), CBucket = (bucket*P + zbucket*A), CLevel = (level*P + zlevel*A), CSince = (since*P + zsince*A), CInvRemain = (invremain*P + zinvremain*A), CBlockages = (blockages*P + zblockages*A), V = (zid*Xid + zbucket*Xbucket + zlevel*Xlevel + zsince*Xsince + zinvremain*Xinvremain + zblockages*Xblockages + negzQ*A), // Prove bucket is same bucket used in BP BP = (bucket*H), // Prove CLevel encodes a value of 3 or 4 // First prove g is a bit by proving that g = g^2 CG = (g*P + zg*A), CGsq = (g*CG + wg*A), CGsq = (g*P + yg*A) // The verifier will compute CG = CLevel - 3P } pub fn request(lox_cred: &cred::Lox, lox_pub: &IssuerPubKey) -> Result { let A: &RistrettoPoint = &CMZ_A; let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE; // TODO: Where should this go? For efficiency, this should probably be global let date: u32 = time::OffsetDateTime::now_utc() .date() .to_julian_day() .try_into() .unwrap(); let H = compute_H(date); let Htable: RistrettoBasepointTable = RistrettoBasepointTable::create(&H); // Ensure that the credential can be correctly shown: it must be the case // that trust_level is 3 or 4 let level: u32 = match scalar_u32(&lox_cred.trust_level) { Some(v) => v, None => return Err(ProofError::VerificationFailure), }; // level must be 3 + one bit or we need to add more bits if level < 3 || level > 4 { return Err(ProofError::VerificationFailure); } // Blind showing the Lox credential // Reblind P and Q let mut rng = rand::thread_rng(); let t = Scalar::random(&mut rng); let P = t * lox_cred.P; let Q = t * lox_cred.Q; // Form Pedersen commitments to the blinded attributes let zid = Scalar::random(&mut rng); let zbucket = Scalar::random(&mut rng); let zlevel = Scalar::random(&mut rng); let zsince = Scalar::random(&mut rng); let zinvremain = Scalar::random(&mut rng); let zblockages = Scalar::random(&mut rng); let CId = lox_cred.id * P + &zid * Atable; let CBucket = lox_cred.bucket * P + &zbucket * Atable; let CLevel = lox_cred.trust_level * P + &zlevel * Atable; let CSince = lox_cred.level_since * P + &zsince * Atable; let CInvRemain = lox_cred.invites_remaining * P + &zinvremain * Atable; let CBlockages = lox_cred.blockages * P + &zblockages * Atable; // Form a Pedersen commitment to the MAC Q // We flip the sign of zQ from that of the Hyphae paper so that // the lox_zkp has a "+" instead of a "-", as that's what the lox_zkp // macro supports. let negzQ = Scalar::random(&mut rng); let CQ = Q - &negzQ * Atable; // Compute the "error factor" let V = zid * lox_pub.X[1] + zbucket * lox_pub.X[2] + zlevel * lox_pub.X[3] + zsince * lox_pub.X[4] + zinvremain * lox_pub.X[5] + zblockages * lox_pub.X[6] + &negzQ * Atable; // Compute BP for proving knowledge of bucket let BP = &lox_cred.bucket * &Htable; // Proof that 3 <= trust_level let g: Scalar = (level - 3).into(); // Pick random factor for the Pedersen commitment let wg = Scalar::random(&mut rng); // zg equals zlevel so that // 3*P + CG // = 3*P + (g*P + zg*A) // = (3+g)*P + zlevel*A // = level*P + zlevel*A // = CLevel let zg = zlevel; let yg = wg + g * zg; let CG = g * P + &zg * Atable; let CGsq = g * P + &yg * Atable; // Construct the proof let mut transcript = Transcript::new(b"positive report request"); let piUser = requestproof::prove_compact( &mut transcript, requestproof::ProveAssignments { A, P: &P, CId: &CId, CBucket: &CBucket, CLevel: &CLevel, CSince: &CSince, CInvRemain: &CInvRemain, CBlockages: &CBlockages, V: &V, Xid: &lox_pub.X[1], Xbucket: &lox_pub.X[2], Xlevel: &lox_pub.X[3], Xsince: &lox_pub.X[4], Xinvremain: &lox_pub.X[5], Xblockages: &lox_pub.X[6], H: &H, BP: &BP, CG: &CG, CGsq: &CGsq, id: &lox_cred.id, bucket: &lox_cred.bucket, level: &lox_cred.trust_level, since: &lox_cred.level_since, invremain: &lox_cred.invites_remaining, blockages: &lox_cred.blockages, zid: &zid, zbucket: &zbucket, zlevel: &zlevel, zsince: &zsince, zinvremain: &zinvremain, zblockages: &zblockages, negzQ: &negzQ, g: &g, zg: &zg, wg: &wg, yg: &yg, }, ) .0; Ok(Request { P, CId, CBucket, CLevel, CSince, CInvRemain, CBlockages, CQ, date, BP, CGsq, piUser, }) } impl BridgeAuth { /// Receive a positive report request pub fn handle_positive_report( &mut self, req: Request, Htable: &RistrettoBasepointTable, ) -> Result<(), ProofError> { let A: &RistrettoPoint = &CMZ_A; let H = Htable.basepoint(); if req.P.is_identity() { return Err(ProofError::VerificationFailure); } // Recompute the "error factor" using knowledge of our own // (the issuer's) private key instead of knowledge of the // hidden attributes let Vprime = self.lox_priv.x[0] * req.P + self.lox_priv.x[1] * req.CId + self.lox_priv.x[2] * req.CBucket + self.lox_priv.x[3] * req.CLevel + self.lox_priv.x[4] * req.CSince + self.lox_priv.x[5] * req.CInvRemain + self.lox_priv.x[6] * req.CBlockages - req.CQ; // Recompute CG let CG = req.CLevel - Scalar::from(3 as u8) * req.P; // Verify the zkp let mut transcript = Transcript::new(b"positive report request"); requestproof::verify_compact( &req.piUser, &mut transcript, requestproof::VerifyAssignments { A: &A.compress(), P: &req.P.compress(), CId: &req.CId.compress(), CBucket: &req.CBucket.compress(), CLevel: &req.CLevel.compress(), CSince: &req.CSince.compress(), CInvRemain: &req.CInvRemain.compress(), CBlockages: &req.CBlockages.compress(), V: &Vprime.compress(), Xid: &self.lox_pub.X[1].compress(), Xbucket: &self.lox_pub.X[2].compress(), Xlevel: &self.lox_pub.X[3].compress(), Xsince: &self.lox_pub.X[4].compress(), Xinvremain: &self.lox_pub.X[5].compress(), Xblockages: &self.lox_pub.X[6].compress(), H: &H.compress(), BP: &req.BP.compress(), CG: &CG.compress(), CGsq: &req.CGsq.compress(), }, )?; Ok(()) } }