diff --git a/crates/lox-library/Cargo.toml b/crates/lox-library/Cargo.toml index 0a53f27..ea88540 100644 --- a/crates/lox-library/Cargo.toml +++ b/crates/lox-library/Cargo.toml @@ -7,8 +7,8 @@ edition = "2018" [dependencies] curve25519-dalek = { package = "curve25519-dalek-ng", version = "3", default-features = false, features = ["serde", "std"] } ed25519-dalek = "1" -zkp = { version = "0.8", features = ["debug-transcript"] } -# zkp = "0.8" +# zkp = { version = "0.8", features = ["debug-transcript"] } +zkp = "0.8" bincode = "1" rand = "0.7" serde = "1" diff --git a/crates/lox-library/src/lib.rs b/crates/lox-library/src/lib.rs index f6c3de4..51961d4 100644 --- a/crates/lox-library/src/lib.rs +++ b/crates/lox-library/src/lib.rs @@ -206,8 +206,10 @@ pub struct BridgeAuth { /// Duplicate filter for open invitations openinv_filter: dup_filter::DupFilter, - /// Duplicate filter for credential ids + /// Duplicate filter for Lox credential ids id_filter: dup_filter::DupFilter, + /// Duplicate filter for Invitation credential ids + inv_id_filter: dup_filter::DupFilter, /// Duplicate filter for trust promotions (from untrusted level 0 to /// trusted level 1) trust_promotion_filter: dup_filter::DupFilter, @@ -246,6 +248,7 @@ impl BridgeAuth { migration_table: Default::default(), openinv_filter: Default::default(), id_filter: Default::default(), + inv_id_filter: Default::default(), trust_promotion_filter: Default::default(), time_offset: time::Duration::zero(), } diff --git a/crates/lox-library/src/proto/level_up.rs b/crates/lox-library/src/proto/level_up.rs index ee64b80..553cdcf 100644 --- a/crates/lox-library/src/proto/level_up.rs +++ b/crates/lox-library/src/proto/level_up.rs @@ -729,8 +729,8 @@ impl BridgeAuth { - req.CQ_reach; // Recompute CG0 using Horner's method - let unt: Scalar = LEVEL_INTERVAL[level].into(); - let CG0prime = (today - unt) * req.P + let interval: Scalar = LEVEL_INTERVAL[level].into(); + let CG0prime = (today - interval) * req.P - req.CSince - pt_dbl( &(pt_dbl( diff --git a/crates/lox-library/src/proto/redeem_invite.rs b/crates/lox-library/src/proto/redeem_invite.rs index a5373d6..20570e3 100644 --- a/crates/lox-library/src/proto/redeem_invite.rs +++ b/crates/lox-library/src/proto/redeem_invite.rs @@ -44,7 +44,7 @@ pub const INVITATION_EXPIRY: u32 = 15; pub struct Request { // Fields for showing the Invitation credential P: RistrettoPoint, - id: Scalar, + inv_id: Scalar, CDate: RistrettoPoint, CBucket: RistrettoPoint, CBlockages: RistrettoPoint, @@ -138,6 +138,34 @@ define_proof! { // + 8*CG3) as its value of CG0. } +define_proof! { + blindissue, + "Redeem Invite Issuing", + (x0, x0tilde, xid, xbucket, xlevel, xsince, xblockages, + s, b, tid, tbucket, tblockages), + (P, EncQ0, EncQ1, X0, Xid, Xbucket, Xlevel, Xsince, Xblockages, + Psince, TId, TBucket, TBlockages, + D, EncId0, EncId1, EncBucket0, EncBucket1, EncBlockages0, EncBlockages1), + (A, B): + Xid = (xid*A), + Xbucket = (xbucket*A), + Xlevel = (xlevel*A), + Xsince = (xsince*A), + Xblockages = (xblockages*A), + X0 = (x0*B + x0tilde*A), + P = (b*B), + TId = (b*Xid), + TId = (tid*A), + TBucket = (b*Xbucket), + TBucket = (tbucket*A), + TBlockages = (b*Xblockages), + TBlockages = (tblockages*A), + EncQ0 = (s*B + tid*EncId0 + tbucket*EncBucket0 + tblockages*EncBlockages0), + // level=1 (so Plevel = P) and invremain=0 (so the term is omitted) + EncQ1 = (s*D + tid*EncId1 + tbucket*EncBucket1 + + tblockages*EncBlockages1 + x0*P + xlevel*P + xsince*Psince) +} + pub fn request( inv_cred: &cred::Invitation, invitation_pub: &IssuerPubKey, @@ -234,9 +262,9 @@ pub fn request( let wg3 = Scalar::random(&mut rng); // Compute zg0 to cancel things out as - // zg0 = -(zdate + 2*zg1 + 4*zg2 + 8*zg3) + // zg0 = zdate - (2*zg1 + 4*zg2 + 8*zg3) // but use Horner's method - let zg0 = -(scalar_dbl(&(scalar_dbl(&(scalar_dbl(&zg3) + zg2)) + zg1)) + zdate); + let zg0 = zdate - scalar_dbl(&(scalar_dbl(&(scalar_dbl(&zg3) + zg2)) + zg1)); let yg0 = wg0 + g0 * zg0; let yg1 = wg1 + g1 * zg1; @@ -318,7 +346,7 @@ pub fn request( Ok(( Request { P, - id: inv_cred.inv_id, + inv_id: inv_cred.inv_id, CDate, CBucket, CBlockages, @@ -348,3 +376,240 @@ pub fn request( }, )) } + +impl BridgeAuth { + /// Receive a redeem invite request + pub fn handle_redeem_invite(&mut self, req: Request) -> Result { + let A: &RistrettoPoint = &CMZ_A; + let B: &RistrettoPoint = &CMZ_B; + let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE; + let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE; + + if req.P.is_identity() { + return Err(ProofError::VerificationFailure); + } + + let today: Scalar = self.today().into(); + + // Recompute the "error factor" using knowledge of our own + // (the issuer's) private key instead of knowledge of the + // hidden attributes + let Vprime = (self.invitation_priv.x[0] + self.invitation_priv.x[1] * req.inv_id) * req.P + + self.invitation_priv.x[2] * req.CDate + + self.invitation_priv.x[3] * req.CBucket + + self.invitation_priv.x[4] * req.CBlockages + - req.CQ; + + // Recompute CG0 using Horner's method + let expiry: Scalar = INVITATION_EXPIRY.into(); + let CG0prime = (expiry - today) * req.P + req.CDate + - pt_dbl(&(pt_dbl(&(pt_dbl(&req.CG3) + req.CG2)) + req.CG1)); + + // Verify the ZKP + let mut transcript = Transcript::new(b"redeem invite request"); + requestproof::verify_compact( + &req.piUser, + &mut transcript, + requestproof::VerifyAssignments { + A: &A.compress(), + B: &B.compress(), + P: &req.P.compress(), + CDate: &req.CDate.compress(), + CBucket: &req.CBucket.compress(), + CBlockages: &req.CBlockages.compress(), + V: &Vprime.compress(), + Xdate: &self.invitation_pub.X[2].compress(), + Xbucket: &self.invitation_pub.X[3].compress(), + Xblockages: &self.invitation_pub.X[4].compress(), + D: &req.D.compress(), + EncIdClient0: &req.EncIdClient.0.compress(), + EncIdClient1: &req.EncIdClient.1.compress(), + EncBucket0: &req.EncBucket.0.compress(), + EncBucket1: &req.EncBucket.1.compress(), + EncBlockages0: &req.EncBlockages.0.compress(), + EncBlockages1: &req.EncBlockages.1.compress(), + CG0: &CG0prime.compress(), + CG1: &req.CG1.compress(), + CG2: &req.CG2.compress(), + CG3: &req.CG3.compress(), + CG0sq: &req.CG0sq.compress(), + CG1sq: &req.CG1sq.compress(), + CG2sq: &req.CG2sq.compress(), + CG3sq: &req.CG3sq.compress(), + }, + )?; + + // Ensure the id has not been seen before, and add it to the + // invite id seen list. + if self.inv_id_filter.filter(&req.inv_id) == SeenType::Seen { + return Err(ProofError::VerificationFailure); + } + + // Blind issuing of the new Lox credential + + // Choose a random server id component to add to the client's + // (blinded) id component + let mut rng = rand::thread_rng(); + let id_server = Scalar::random(&mut rng); + let EncId = (req.EncIdClient.0, req.EncIdClient.1 + &id_server * Btable); + + // The trust level for invitees is always 1 + let level = Scalar::one(); + + // The invites remaining for invitees is always 0 (as + // appropriate for trust level 1), so we don't need to actually + // construct it + + // Compute the MAC on the visible attributes + let b = Scalar::random(&mut rng); + let P = &b * Btable; + let QHc = + (self.lox_priv.x[0] + self.lox_priv.x[3] * level + self.lox_priv.x[4] * today) * P; + + // El Gamal encrypt it to the public key req.D + let s = Scalar::random(&mut rng); + let EncQHc = (&s * Btable, QHc + s * req.D); + + // Homomorphically compute the part of the MAC corresponding to + // the blinded attributes + let tid = self.lox_priv.x[1] * b; + let TId = &tid * Atable; + let EncQId = (tid * EncId.0, tid * EncId.1); + let tbucket = self.lox_priv.x[2] * b; + let TBucket = &tbucket * Atable; + let EncQBucket = (tbucket * req.EncBucket.0, tbucket * req.EncBucket.1); + let tblockages = self.lox_priv.x[6] * b; + let TBlockages = &tblockages * Atable; + let EncQBlockages = ( + tblockages * req.EncBlockages.0, + tblockages * req.EncBlockages.1, + ); + + let EncQ = ( + EncQHc.0 + EncQId.0 + EncQBucket.0 + EncQBlockages.0, + EncQHc.1 + EncQId.1 + EncQBucket.1 + EncQBlockages.1, + ); + + let mut transcript = Transcript::new(b"redeem invite issuing"); + let piBlindIssue = blindissue::prove_compact( + &mut transcript, + blindissue::ProveAssignments { + A: &A, + B: &B, + P: &P, + EncQ0: &EncQ.0, + EncQ1: &EncQ.1, + X0: &self.lox_pub.X[0], + Xid: &self.lox_pub.X[1], + Xbucket: &self.lox_pub.X[2], + Xlevel: &self.lox_pub.X[3], + Xsince: &self.lox_pub.X[4], + Xblockages: &self.lox_pub.X[6], + Psince: &(today * P), + TId: &TId, + TBucket: &TBucket, + TBlockages: &TBlockages, + D: &req.D, + EncId0: &EncId.0, + EncId1: &EncId.1, + EncBucket0: &req.EncBucket.0, + EncBucket1: &req.EncBucket.1, + EncBlockages0: &req.EncBlockages.0, + EncBlockages1: &req.EncBlockages.1, + x0: &self.lox_priv.x[0], + x0tilde: &self.lox_priv.x0tilde, + xid: &self.lox_priv.x[1], + xbucket: &self.lox_priv.x[2], + xlevel: &self.lox_priv.x[3], + xsince: &self.lox_priv.x[4], + xblockages: &self.lox_priv.x[6], + s: &s, + b: &b, + tid: &tid, + tbucket: &tbucket, + tblockages: &tblockages, + }, + ) + .0; + + Ok(Response { + P, + EncQ, + id_server, + level_since: today, + TId, + TBucket, + TBlockages, + piBlindIssue, + }) + } +} + +/// Handle the response to the request, producing the new Lox credential +/// if successful. +pub fn handle_response( + state: State, + resp: Response, + lox_pub: &IssuerPubKey, +) -> Result { + let A: &RistrettoPoint = &CMZ_A; + let B: &RistrettoPoint = &CMZ_B; + let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE; + + if resp.P.is_identity() { + return Err(ProofError::VerificationFailure); + } + + // Add the server's contribution to the id to our own, both in plain + // and encrypted form + let id = state.id_client + resp.id_server; + let EncId = ( + state.EncIdClient.0, + state.EncIdClient.1 + &resp.id_server * Btable, + ); + + // Verify the proof + let mut transcript = Transcript::new(b"redeem invite issuing"); + blindissue::verify_compact( + &resp.piBlindIssue, + &mut transcript, + blindissue::VerifyAssignments { + A: &A.compress(), + B: &B.compress(), + P: &resp.P.compress(), + EncQ0: &resp.EncQ.0.compress(), + EncQ1: &resp.EncQ.1.compress(), + X0: &lox_pub.X[0].compress(), + Xid: &lox_pub.X[1].compress(), + Xbucket: &lox_pub.X[2].compress(), + Xlevel: &lox_pub.X[3].compress(), + Xsince: &lox_pub.X[4].compress(), + Xblockages: &lox_pub.X[6].compress(), + Psince: &(resp.level_since * resp.P).compress(), + TId: &resp.TId.compress(), + TBucket: &resp.TBucket.compress(), + TBlockages: &resp.TBlockages.compress(), + D: &state.D.compress(), + EncId0: &EncId.0.compress(), + EncId1: &EncId.1.compress(), + EncBucket0: &state.EncBucket.0.compress(), + EncBucket1: &state.EncBucket.1.compress(), + EncBlockages0: &state.EncBlockages.0.compress(), + EncBlockages1: &state.EncBlockages.1.compress(), + }, + )?; + + // Decrypt EncQ + let Q = resp.EncQ.1 - (state.d * resp.EncQ.0); + + Ok(cred::Lox { + P: resp.P, + Q, + id, + bucket: state.bucket, + trust_level: Scalar::one(), + level_since: resp.level_since, + invites_remaining: Scalar::zero(), + blockages: state.blockages, + }) +} diff --git a/crates/lox-library/src/tests.rs b/crates/lox-library/src/tests.rs index 71aaade..1778c50 100644 --- a/crates/lox-library/src/tests.rs +++ b/crates/lox-library/src/tests.rs @@ -245,7 +245,8 @@ fn test_issue_invite() { fn redeem_invite(ba: &mut BridgeAuth, inv: &cred::Invitation) -> cred::Lox { let (req, state) = redeem_invite::request(&inv, &ba.invitation_pub, ba.today()).unwrap(); - panic!("Not finished") + let resp = ba.handle_redeem_invite(req).unwrap(); + redeem_invite::handle_response(state, resp, &ba.lox_pub).unwrap() } #[test] @@ -266,6 +267,9 @@ fn test_redeem_invite() { println!("cred3 = {:?}", cred3); println!("invite = {:?}", invite); + // Time passes + ba.advance_days(12); + let cred4 = redeem_invite(&mut ba, &invite); assert!(ba.verify_lox(&cred4)); println!("cred4 = {:?}", cred4);