diff --git a/crates/lox-library/src/lib.rs b/crates/lox-library/src/lib.rs index 2fbf9b0..f6c3de4 100644 --- a/crates/lox-library/src/lib.rs +++ b/crates/lox-library/src/lib.rs @@ -401,6 +401,7 @@ pub mod proto { pub mod level_up; pub mod migration; pub mod open_invite; + pub mod redeem_invite; pub mod trust_promotion; } diff --git a/crates/lox-library/src/proto/redeem_invite.rs b/crates/lox-library/src/proto/redeem_invite.rs new file mode 100644 index 0000000..a5373d6 --- /dev/null +++ b/crates/lox-library/src/proto/redeem_invite.rs @@ -0,0 +1,350 @@ +/*! 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 + +*/ + +use curve25519_dalek::ristretto::RistrettoBasepointTable; +use curve25519_dalek::ristretto::RistrettoPoint; +use curve25519_dalek::scalar::Scalar; +use curve25519_dalek::traits::IsIdentity; + +use zkp::CompactProof; +use zkp::ProofError; +use zkp::Transcript; + +use super::super::cred; +use super::super::dup_filter::SeenType; +use super::super::{pt_dbl, scalar_dbl, scalar_u32}; +use super::super::{BridgeAuth, IssuerPubKey}; +use super::super::{CMZ_A, CMZ_A_TABLE, CMZ_B, CMZ_B_TABLE}; + +/// 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; + +pub struct Request { + // Fields for showing the Invitation credential + P: RistrettoPoint, + id: Scalar, + CDate: RistrettoPoint, + CBucket: RistrettoPoint, + CBlockages: RistrettoPoint, + CQ: RistrettoPoint, + + // Fields for the inequality proof + // date + INVITATION_EXPIRY >= today + CG1: RistrettoPoint, + CG2: RistrettoPoint, + CG3: RistrettoPoint, + CG0sq: RistrettoPoint, + CG1sq: RistrettoPoint, + CG2sq: RistrettoPoint, + CG3sq: RistrettoPoint, + + // Fields for user blinding of the Lox credential to be issued + D: RistrettoPoint, + EncIdClient: (RistrettoPoint, RistrettoPoint), + EncBucket: (RistrettoPoint, RistrettoPoint), + EncBlockages: (RistrettoPoint, RistrettoPoint), + + // The combined ZKP + piUser: CompactProof, +} + +#[derive(Debug)] +pub struct State { + d: Scalar, + D: RistrettoPoint, + EncIdClient: (RistrettoPoint, RistrettoPoint), + EncBucket: (RistrettoPoint, RistrettoPoint), + EncBlockages: (RistrettoPoint, RistrettoPoint), + id_client: Scalar, + bucket: Scalar, + blockages: Scalar, +} + +pub struct Response { + // The fields for the new Lox credential; the new trust level is 1 + // and the new invites_remaining is 0, so we don't have to include + // them here explicitly + P: RistrettoPoint, + EncQ: (RistrettoPoint, RistrettoPoint), + id_server: Scalar, + level_since: Scalar, + TId: RistrettoPoint, + TBucket: RistrettoPoint, + TBlockages: RistrettoPoint, + + // The ZKP + piBlindIssue: CompactProof, +} + +define_proof! { + requestproof, + "Redeem Invite Request", + (date, bucket, blockages, zdate, zbucket, zblockages, negzQ, + d, eid_client, ebucket, eblockages, id_client, + g0, g1, g2, g3, + zg0, zg1, zg2, zg3, + wg0, wg1, wg2, wg3, + yg0, yg1, yg2, yg3), + (P, CDate, CBucket, CBlockages, V, Xdate, Xbucket, Xblockages, + D, EncIdClient0, EncIdClient1, EncBucket0, EncBucket1, + EncBlockages0, EncBlockages1, + CG0, CG1, CG2, CG3, + CG0sq, CG1sq, CG2sq, CG3sq), + (A, B): + // Blind showing of the Invitation credential + CDate = (date*P + zdate*A), + CBucket = (bucket*P + zbucket*A), + CBlockages = (blockages*P + zblockages*A), + // User blinding of the Lox credential to be issued + D = (d*B), + EncIdClient0 = (eid_client*B), + EncIdClient1 = (id_client*B + eid_client*D), + EncBucket0 = (ebucket*B), + EncBucket1 = (bucket*B + ebucket*D), + EncBlockages0 = (eblockages*B), + EncBlockages1 = (blockages*B + eblockages*D), + // Prove CDate encodes a value at most INVITATION_EXPIRY + // days ago: first prove each of g0, ..., g3 is a bit by + // proving that gi = gi^2 + CG0 = (g0*P + zg0*A), CG0sq = (g0*CG0 + wg0*A), CG0sq = (g0*P + yg0*A), + CG1 = (g1*P + zg1*A), CG1sq = (g1*CG1 + wg1*A), CG1sq = (g1*P + yg1*A), + CG2 = (g2*P + zg2*A), CG2sq = (g2*CG2 + wg2*A), CG2sq = (g2*P + yg2*A), + CG3 = (g3*P + zg3*A), CG3sq = (g3*CG3 + wg3*A), CG3sq = (g3*P + yg3*A) + // Then we'll check that today*P + CG0 + 2*CG1 + 4*CG2 + 8*CG3 = + // CDate + INVITATION_EXPIRY*P by having the verifier + // plug in CDate + INVITATION_EXPIRY*P - (today*P + 2*CG1 + 4*CG2 + // + 8*CG3) as its value of CG0. +} + +pub fn request( + inv_cred: &cred::Invitation, + invitation_pub: &IssuerPubKey, + today: u32, +) -> Result<(Request, State), ProofError> { + let A: &RistrettoPoint = &CMZ_A; + let B: &RistrettoPoint = &CMZ_B; + let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE; + let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE; + + // Ensure the credential can be correctly shown: it must be the case + // that date + INVITATION_EXPIRY >= today. + let date: u32 = match scalar_u32(&inv_cred.date) { + Some(v) => v, + None => return Err(ProofError::VerificationFailure), + }; + if date + INVITATION_EXPIRY < today { + return Err(ProofError::VerificationFailure); + } + 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(ProofError::VerificationFailure); + } + + // Blind showing the Invitation credential + + // Reblind P and Q + let mut rng = rand::thread_rng(); + let t = Scalar::random(&mut rng); + let P = t * inv_cred.P; + let Q = t * inv_cred.Q; + + // Form Pedersen commitments to the blinded attributes + let zdate = Scalar::random(&mut rng); + let zbucket = Scalar::random(&mut rng); + let zblockages = Scalar::random(&mut rng); + let CDate = inv_cred.date * P + &zdate * Atable; + let CBucket = inv_cred.bucket * P + &zbucket * Atable; + let CBlockages = inv_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 ZKP has a "+" instead of a "-", as that's what the zkp + // macro supports. + let negzQ = Scalar::random(&mut rng); + let CQ = Q - &negzQ * Atable; + + // Compute the "error factor" + let V = zdate * invitation_pub.X[2] + + zbucket * invitation_pub.X[3] + + zblockages * invitation_pub.X[4] + + &negzQ * Atable; + + // User blinding for the Lox certificate to be issued + + // Pick an ElGamal keypair + let d = Scalar::random(&mut rng); + let D = &d * Btable; + + // Pick a random client component of the id + let id_client = Scalar::random(&mut rng); + + // Encrypt it (times the basepoint B) to the ElGamal public key D we + // just created + let eid_client = Scalar::random(&mut rng); + let EncIdClient = (&eid_client * Btable, &id_client * Btable + eid_client * D); + + // Encrypt the other blinded fields (times B) to D as well + let ebucket = Scalar::random(&mut rng); + let EncBucket = (&ebucket * Btable, &inv_cred.bucket * Btable + ebucket * D); + let eblockages = Scalar::random(&mut rng); + let EncBlockages = ( + &eblockages * Btable, + &inv_cred.blockages * Btable + eblockages * D, + ); + + // The range proof that 0 <= diffdays <= 15 + + // Extract the 4 bits from diffdays + let g0: Scalar = (diffdays & 1).into(); + let g1: Scalar = ((diffdays >> 1) & 1).into(); + let g2: Scalar = ((diffdays >> 2) & 1).into(); + let g3: Scalar = ((diffdays >> 3) & 1).into(); + + // Pick random factors for the Pedersen commitments + let wg0 = Scalar::random(&mut rng); + let zg1 = Scalar::random(&mut rng); + let wg1 = Scalar::random(&mut rng); + let zg2 = Scalar::random(&mut rng); + let wg2 = Scalar::random(&mut rng); + let zg3 = Scalar::random(&mut rng); + let wg3 = Scalar::random(&mut rng); + + // Compute zg0 to cancel things out as + // 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 yg0 = wg0 + g0 * zg0; + let yg1 = wg1 + g1 * zg1; + let yg2 = wg2 + g2 * zg2; + let yg3 = wg3 + g3 * zg3; + + let CG0 = g0 * P + &zg0 * Atable; + let CG1 = g1 * P + &zg1 * Atable; + let CG2 = g2 * P + &zg2 * Atable; + let CG3 = g3 * P + &zg3 * Atable; + + let CG0sq = g0 * P + &yg0 * Atable; + let CG1sq = g1 * P + &yg1 * Atable; + let CG2sq = g2 * P + &yg2 * Atable; + let CG3sq = g3 * P + &yg3 * Atable; + + // Construct the proof + let mut transcript = Transcript::new(b"redeem invite request"); + let piUser = requestproof::prove_compact( + &mut transcript, + requestproof::ProveAssignments { + A: &A, + B: &B, + P: &P, + CDate: &CDate, + CBucket: &CBucket, + CBlockages: &CBlockages, + V: &V, + Xdate: &invitation_pub.X[2], + Xbucket: &invitation_pub.X[3], + Xblockages: &invitation_pub.X[4], + D: &D, + EncIdClient0: &EncIdClient.0, + EncIdClient1: &EncIdClient.1, + EncBucket0: &EncBucket.0, + EncBucket1: &EncBucket.1, + EncBlockages0: &EncBlockages.0, + EncBlockages1: &EncBlockages.1, + CG0: &CG0, + CG1: &CG1, + CG2: &CG2, + CG3: &CG3, + CG0sq: &CG0sq, + CG1sq: &CG1sq, + CG2sq: &CG2sq, + CG3sq: &CG3sq, + date: &inv_cred.date, + bucket: &inv_cred.bucket, + blockages: &inv_cred.blockages, + zdate: &zdate, + zbucket: &zbucket, + zblockages: &zblockages, + negzQ: &negzQ, + d: &d, + eid_client: &eid_client, + ebucket: &ebucket, + eblockages: &eblockages, + id_client: &id_client, + g0: &g0, + g1: &g1, + g2: &g2, + g3: &g3, + zg0: &zg0, + zg1: &zg1, + zg2: &zg2, + zg3: &zg3, + wg0: &wg0, + wg1: &wg1, + wg2: &wg2, + wg3: &wg3, + yg0: &yg0, + yg1: &yg1, + yg2: &yg2, + yg3: &yg3, + }, + ) + .0; + + Ok(( + Request { + P, + id: inv_cred.inv_id, + CDate, + CBucket, + CBlockages, + CQ, + D, + EncIdClient, + EncBucket, + EncBlockages, + CG1, + CG2, + CG3, + CG0sq, + CG1sq, + CG2sq, + CG3sq, + piUser, + }, + State { + d, + D, + EncIdClient, + EncBucket, + EncBlockages, + id_client, + bucket: inv_cred.bucket, + blockages: inv_cred.blockages, + }, + )) +} diff --git a/crates/lox-library/src/tests.rs b/crates/lox-library/src/tests.rs index 183ec2d..71aaade 100644 --- a/crates/lox-library/src/tests.rs +++ b/crates/lox-library/src/tests.rs @@ -201,8 +201,29 @@ fn test_level_up() { assert!(ba.verify_lox(&cred4)); } +fn issue_invite(ba: &mut BridgeAuth, cred: &cred::Lox) -> (cred::Lox, cred::Invitation) { + // Read the bucket in the credential to get today's Bucket + // Reachability credential + let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap(); + let encbuckets = ba.enc_bridge_table(); + let bucket = + bridge_table::BridgeTable::decrypt_bucket(id, &key, &encbuckets[id as usize]).unwrap(); + let reachcred = bucket.1.unwrap(); + + let (req, state) = issue_invite::request( + &cred, + &reachcred, + &ba.lox_pub, + &ba.reachability_pub, + ba.today(), + ) + .unwrap(); + let resp = ba.handle_issue_invite(req).unwrap(); + issue_invite::handle_response(state, resp, &ba.lox_pub, &ba.invitation_pub).unwrap() +} + #[test] -fn test_issue_inv() { +fn test_issue_invite() { let (bdb, mut ba) = setup(); let cred1 = level0_migration(&bdb, &mut ba); assert!(scalar_u32(&cred1.trust_level).unwrap() == 1); @@ -215,27 +236,37 @@ fn test_issue_inv() { println!("cred2 = {:?}", cred2); assert!(ba.verify_lox(&cred2)); - // Read the bucket in the credential to get today's Bucket - // Reachability credential - let (id, key) = bridge_table::from_scalar(cred2.bucket).unwrap(); - let encbuckets = ba.enc_bridge_table(); - let bucket = - bridge_table::BridgeTable::decrypt_bucket(id, &key, &encbuckets[id as usize]).unwrap(); - let reachcred = bucket.1.unwrap(); - - let (req, state) = issue_invite::request( - &cred2, - &reachcred, - &ba.lox_pub, - &ba.reachability_pub, - ba.today(), - ) - .unwrap(); - let resp = ba.handle_issue_invite(req).unwrap(); - let (cred3, invite) = - issue_invite::handle_response(state, resp, &ba.lox_pub, &ba.invitation_pub).unwrap(); + let (cred3, invite) = issue_invite(&mut ba, &cred2); assert!(ba.verify_lox(&cred3)); assert!(ba.verify_invitation(&invite)); println!("cred3 = {:?}", cred3); println!("invite = {:?}", 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") +} + +#[test] +fn test_redeem_invite() { + let (bdb, mut ba) = setup(); + let cred1 = level0_migration(&bdb, &mut ba); + assert!(scalar_u32(&cred1.trust_level).unwrap() == 1); + + // Time passes + ba.advance_days(20); + + let cred2 = level_up(&mut ba, &cred1); + assert!(scalar_u32(&cred2.trust_level).unwrap() == 2); + println!("cred2 = {:?}", cred2); + assert!(ba.verify_lox(&cred2)); + + let (cred3, invite) = issue_invite(&mut ba, &cred2); + println!("cred3 = {:?}", cred3); + println!("invite = {:?}", invite); + + let cred4 = redeem_invite(&mut ba, &invite); + assert!(ba.verify_lox(&cred4)); + println!("cred4 = {:?}", cred4); +}