lox/crates/lox-library/src/proto/positive_report.rs

295 lines
9.3 KiB
Rust

/*! 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::<Sha512>(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<Request, ProofError> {
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(())
}
}