From d924b98060c6e803461f878e17e4d88039917a2e Mon Sep 17 00:00:00 2001 From: Ian Goldberg Date: Mon, 3 May 2021 19:05:42 -0400 Subject: [PATCH] The request message of the issue invitation protocol --- crates/lox-library/src/cred.rs | 18 + crates/lox-library/src/lib.rs | 26 ++ crates/lox-library/src/proto/issue_invite.rs | 443 +++++++++++++++++++ crates/lox-library/src/proto/level_up.rs | 6 +- crates/lox-library/src/tests.rs | 32 ++ 5 files changed, 523 insertions(+), 2 deletions(-) create mode 100644 crates/lox-library/src/proto/issue_invite.rs diff --git a/crates/lox-library/src/cred.rs b/crates/lox-library/src/cred.rs index 205a3f6..b070854 100644 --- a/crates/lox-library/src/cred.rs +++ b/crates/lox-library/src/cred.rs @@ -75,3 +75,21 @@ pub struct BucketReachability { pub date: Scalar, pub bucket: Scalar, } + +/// The Invitation credential. +/// +/// These credentials allow a Lox user (the inviter) of sufficient trust +/// (level 2 or higher) to invite someone else (the invitee) to join the +/// system. The invitee ends up at trust level 1, in the _same bucket_ +/// as the inviter, and inherits the inviter's blockages count (so that +/// you can't clear your blockages count simply by inviting yourself). +/// Invitations expire after some amount of time. +#[derive(Debug)] +pub struct Invitation { + pub P: RistrettoPoint, + pub Q: RistrettoPoint, + pub inv_id: Scalar, + pub date: Scalar, + pub bucket: Scalar, + pub blockages: Scalar, +} diff --git a/crates/lox-library/src/lib.rs b/crates/lox-library/src/lib.rs index 5e5d52b..2fbf9b0 100644 --- a/crates/lox-library/src/lib.rs +++ b/crates/lox-library/src/lib.rs @@ -190,6 +190,10 @@ pub struct BridgeAuth { reachability_priv: IssuerPrivKey, /// The public key for bucket reachability credentials pub reachability_pub: IssuerPubKey, + /// The private key for invitation credentials + invitation_priv: IssuerPrivKey, + /// The public key for invitation credentials + pub invitation_pub: IssuerPubKey, /// The public key of the BridgeDb issuing open invitations pub bridgedb_pub: PublicKey, @@ -224,6 +228,8 @@ impl BridgeAuth { let migrationkey_pub = IssuerPubKey::new(&migrationkey_priv); let reachability_priv = IssuerPrivKey::new(2); let reachability_pub = IssuerPubKey::new(&reachability_priv); + let invitation_priv = IssuerPrivKey::new(4); + let invitation_pub = IssuerPubKey::new(&invitation_priv); Self { lox_priv, lox_pub, @@ -233,6 +239,8 @@ impl BridgeAuth { migrationkey_pub, reachability_priv, reachability_pub, + invitation_priv, + invitation_pub, bridgedb_pub, bridge_table: Default::default(), migration_table: Default::default(), @@ -329,6 +337,23 @@ impl BridgeAuth { Q == cred.Q } + + #[cfg(test)] + /// Verify the MAC on a Invitation credential + pub fn verify_invitation(&self, cred: &cred::Invitation) -> bool { + if cred.P.is_identity() { + return false; + } + + let Q = (self.invitation_priv.x[0] + + cred.inv_id * self.invitation_priv.x[1] + + cred.date * self.invitation_priv.x[2] + + cred.bucket * self.invitation_priv.x[3] + + cred.blockages * self.invitation_priv.x[4]) + * cred.P; + + Q == cred.Q + } } /// Try to extract a u64 from a Scalar @@ -372,6 +397,7 @@ pub fn pt_dbl(P: &RistrettoPoint) -> RistrettoPoint { /// Response. It also adds a handle_* function to the BridgeAuth struct /// that consumes a Request and produces a Result. pub mod proto { + pub mod issue_invite; pub mod level_up; pub mod migration; pub mod open_invite; diff --git a/crates/lox-library/src/proto/issue_invite.rs b/crates/lox-library/src/proto/issue_invite.rs new file mode 100644 index 0000000..bee4bc9 --- /dev/null +++ b/crates/lox-library/src/proto/issue_invite.rs @@ -0,0 +1,443 @@ +/*! A module for the protocol for a user to request the issuing of an +Invitation credential they can pass to someone they know. + +They are allowed to do this as long as their current Lox credentials has +a non-zero "invites_remaining" attribute (which will be decreased by +one), and they have a Bucket Reachability credential for their current +bucket and today's date. (Such credentials are placed daily in the +encrypted bridge table.) + +The user presents their current Lox credential: +- id: revealed +- bucket: blinded +- trust_level: blinded +- level_since: blinded +- invites_remaining: blinded, but proved in ZK that it's not zero +- blockages: blinded + +and a Bucket Reachability credential: +- date: revealed to be today +- bucket: blinded, but proved in ZK that it's the same as in the Lox + credential above + +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 Lox + credential above +- trust_level: blinded, but proved in ZK that it's the same as in the + Lox credential above +- level_since: blinded, but proved in ZK that it's the same as in the + Lox credential above +- invites_remaining: blinded, but proved in ZK that it's one less than + the number in the Lox credential above +- blockages: blinded, but proved in ZK that it's the same as in the + Lox credential above + +and a new Invitation credential to be issued: + +- inv_id: jointly chosen by the user and BA +- date: revealed to be today +- bucket: blinded, but proved in ZK that it's the same as in the Lox + credential above +- blockages: blinded, but proved in ZK that it's the same as in the Lox + 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}; + +pub struct Request { + // Fields for blind showing the Lox credential + P: RistrettoPoint, + id: Scalar, + CBucket: RistrettoPoint, + CLevel: RistrettoPoint, + CSince: RistrettoPoint, + CInvRemain: RistrettoPoint, + CBlockages: RistrettoPoint, + CQ: RistrettoPoint, + + // Fields for blind showing the Bucket Reachability credential + P_reach: RistrettoPoint, + CBucket_reach: RistrettoPoint, + CQ_reach: RistrettoPoint, + + // Fields for user blinding of the Lox credential to be issued + D: RistrettoPoint, + EncIdClient: (RistrettoPoint, RistrettoPoint), + EncBucket: (RistrettoPoint, RistrettoPoint), + EncLevel: (RistrettoPoint, RistrettoPoint), + EncSince: (RistrettoPoint, RistrettoPoint), + EncInvRemain: (RistrettoPoint, RistrettoPoint), + EncBlockages: (RistrettoPoint, RistrettoPoint), + + // Fields for user blinding of the Inivtation credential to be + // issued + EncInvIdClient: (RistrettoPoint, RistrettoPoint), + // The bucket and blockages attributes in the Invitation credential + // issuing protocol can just reuse the exact encryptions as for the + // Lox credential issuing protocol above. + + // The combined ZKP + piUser: CompactProof, +} + +#[derive(Debug)] +pub struct State { + d: Scalar, + D: RistrettoPoint, + EncIdClient: (RistrettoPoint, RistrettoPoint), + EncBucket: (RistrettoPoint, RistrettoPoint), + EncLevel: (RistrettoPoint, RistrettoPoint), + EncSince: (RistrettoPoint, RistrettoPoint), + EncInvRemain: (RistrettoPoint, RistrettoPoint), + EncBlockages: (RistrettoPoint, RistrettoPoint), + EncInvIdClient: (RistrettoPoint, RistrettoPoint), + id_client: Scalar, + bucket: Scalar, + level: Scalar, + since: Scalar, + invremain: Scalar, + blockages: Scalar, + inv_id_client: Scalar, +} + +pub struct Response { + // The fields for the new Lox credential; the new invites_remaining + // is one less than the old value, so we don't have to include it + // here explicitly + P: RistrettoPoint, + EncQ: (RistrettoPoint, RistrettoPoint), + id_server: Scalar, + TId: RistrettoPoint, + TBucket: RistrettoPoint, + TLevel: RistrettoPoint, + TSince: RistrettoPoint, + TInvRemain: RistrettoPoint, + TBlockages: RistrettoPoint, + inv_id_server: Scalar, + TInvId: RistrettoPoint, + + // The ZKP + piBlindIssue: CompactProof, +} + +define_proof! { + requestproof, + "Issue Invite Request", + (bucket, level, since, invremain, blockages, zbucket, zlevel, + zsince, zinvremain, zblockages, negzQ, + zbucket_reach, negzQ_reach, + d, eid_client, ebucket, elevel, esince, einvremain, eblockages, id_client, + inv_id_client, einv_id_client, + invremain_inverse, zinvremain_inverse), + (P, CBucket, CLevel, CSince, CInvRemain, CBlockages, V, Xbucket, + Xlevel, Xsince, Xinvremain, Xblockages, + P_reach, CBucket_reach, V_reach, Xbucket_reach, + D, EncIdClient0, EncIdClient1, EncBucket0, EncBucket1, + EncLevel0, EncLevel1, EncSince0, EncSince1, + EncInvRemain0, EncInvRemain1_plus_B, EncBlockages0, EncBlockages1, + EncInvIdClient0, EncInvIdClient1), + (A, B): + // Blind showing of the Lox credential + 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), + // Proof that invremain is not 0 + P = (invremain_inverse*CInvRemain + zinvremain_inverse*A), + // Blind showing of the Bucket Reachability credential; note the + // same bucket is used in the proof + CBucket_reach = (bucket*P_reach + zbucket_reach*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), + EncLevel0 = (elevel*B), + EncLevel1 = (level*B + elevel*D), + EncSince0 = (esince*B), + EncSince1 = (since*B + esince*D), + EncInvRemain0 = (einvremain*B), + EncInvRemain1_plus_B = (invremain*B + einvremain*D), + EncBlockages0 = (eblockages*B), + EncBlockages1 = (blockages*B + eblockages*D), + // User blinding of the Invitation to be issued + EncInvIdClient0 = (einv_id_client*B), + EncInvIdClient1 = (inv_id_client*B + einv_id_client*D) +} + +pub fn request( + lox_cred: &cred::Lox, + reach_cred: &cred::BucketReachability, + lox_pub: &IssuerPubKey, + reach_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 invites_remaining not be 0 + if lox_cred.invites_remaining == Scalar::zero() { + return Err(ProofError::VerificationFailure); + } + // The buckets in the Lox and Bucket Reachability credentials have + // to match + if lox_cred.bucket != reach_cred.bucket { + return Err(ProofError::VerificationFailure); + } + // The Bucket Reachability credential has to be dated today + let reach_date: u32 = match scalar_u32(&reach_cred.date) { + Some(v) => v, + None => return Err(ProofError::VerificationFailure), + }; + if reach_date != today { + return Err(ProofError::VerificationFailure); + } + // The new invites_remaining + let new_invites_remaining = &lox_cred.invites_remaining - Scalar::one(); + + // 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 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 CBucket = lox_cred.bucket * P + &zbucket * Atable; + let CLevel = lox_cred.bucket * 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 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 = 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; + + // Blind showing the Bucket Reachability credential + + // Reblind P and Q + let t_reach = Scalar::random(&mut rng); + let P_reach = t_reach * reach_cred.P; + let Q_reach = t_reach * reach_cred.Q; + + // Form Pedersen commitments to the blinded attributes + let zbucket_reach = Scalar::random(&mut rng); + let CBucket_reach = reach_cred.bucket * P_reach + &zbucket_reach * 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_reach = Scalar::random(&mut rng); + let CQ_reach = Q_reach - &negzQ_reach * Atable; + + // Compute the "error factor" + let V_reach = zbucket_reach * reach_pub.X[2] + &negzQ_reach * 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, &lox_cred.bucket * Btable + ebucket * D); + let elevel = Scalar::random(&mut rng); + let EncLevel = ( + &elevel * Btable, + &lox_cred.trust_level * Btable + elevel * D, + ); + let esince = Scalar::random(&mut rng); + let EncSince = ( + &esince * Btable, + &lox_cred.level_since * Btable + esince * D, + ); + let einvremain = Scalar::random(&mut rng); + let EncInvRemain = ( + &einvremain * Btable, + &new_invites_remaining * Btable + einvremain * D, + ); + let eblockages = Scalar::random(&mut rng); + let EncBlockages = ( + &eblockages * Btable, + &lox_cred.blockages * Btable + eblockages * D, + ); + + // User blinding for the Invitation certificate to be issued + + // Pick a random client component of the id + let inv_id_client = Scalar::random(&mut rng); + + // Encrypt it (times the basepoint B) to the ElGamal public key D we + // just created + let einv_id_client = Scalar::random(&mut rng); + let EncInvIdClient = ( + &einv_id_client * Btable, + &id_client * Btable + einv_id_client * D, + ); + + // The proof that invites_remaining is not zero. We prove this by + // demonstrating that we know its inverse. + let invremain_inverse = &lox_cred.invites_remaining.invert(); + + let zinvremain_inverse = -zinvremain * invremain_inverse; + + // So now invremain_inverse * CInvRemain + zinvremain_inverse * A = P + + // Construct the proof + let mut transcript = Transcript::new(b"issue invite request"); + let piUser = requestproof::prove_compact( + &mut transcript, + requestproof::ProveAssignments { + A: &A, + B: &B, + P: &P, + CBucket: &CBucket, + CLevel: &CLevel, + CSince: &CSince, + CInvRemain: &CInvRemain, + CBlockages: &CBlockages, + V: &V, + 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], + P_reach: &P_reach, + CBucket_reach: &CBucket_reach, + V_reach: &V_reach, + Xbucket_reach: &reach_pub.X[2], + D: &D, + EncIdClient0: &EncIdClient.0, + EncIdClient1: &EncIdClient.1, + EncBucket0: &EncBucket.0, + EncBucket1: &EncBucket.1, + EncLevel0: &EncLevel.0, + EncLevel1: &EncLevel.1, + EncSince0: &EncSince.0, + EncSince1: &EncSince.1, + EncInvRemain0: &EncInvRemain.0, + EncInvRemain1_plus_B: &(EncInvRemain.1 + B), + EncBlockages0: &EncBlockages.0, + EncBlockages1: &EncBlockages.1, + EncInvIdClient0: &EncInvIdClient.0, + EncInvIdClient1: &EncInvIdClient.1, + bucket: &lox_cred.bucket, + level: &lox_cred.trust_level, + since: &lox_cred.level_since, + invremain: &lox_cred.invites_remaining, + blockages: &lox_cred.blockages, + zbucket: &zbucket, + zlevel: &zlevel, + zsince: &zsince, + zinvremain: &zinvremain, + zblockages: &zblockages, + negzQ: &negzQ, + zbucket_reach: &zbucket_reach, + negzQ_reach: &negzQ_reach, + d: &d, + eid_client: &eid_client, + ebucket: &ebucket, + elevel: &elevel, + esince: &esince, + einvremain: &einvremain, + eblockages: &eblockages, + id_client: &id_client, + inv_id_client: &inv_id_client, + einv_id_client: &einv_id_client, + invremain_inverse: &invremain_inverse, + zinvremain_inverse: &zinvremain_inverse, + }, + ) + .0; + + Ok(( + Request { + P, + id: lox_cred.id, + CBucket, + CLevel, + CSince, + CInvRemain, + CBlockages, + CQ, + P_reach, + CBucket_reach, + CQ_reach, + D, + EncIdClient, + EncBucket, + EncLevel, + EncSince, + EncInvRemain, + EncBlockages, + EncInvIdClient, + piUser, + }, + State { + d, + D, + EncIdClient, + EncBucket, + EncLevel, + EncSince, + EncInvRemain, + EncBlockages, + EncInvIdClient, + id_client, + bucket: lox_cred.bucket, + level: lox_cred.trust_level, + since: lox_cred.level_since, + invremain: new_invites_remaining, + blockages: lox_cred.blockages, + inv_id_client, + }, + )) +} diff --git a/crates/lox-library/src/proto/level_up.rs b/crates/lox-library/src/proto/level_up.rs index 9eafe96..7a1c1bf 100644 --- a/crates/lox-library/src/proto/level_up.rs +++ b/crates/lox-library/src/proto/level_up.rs @@ -76,6 +76,8 @@ pub const LEVEL_INVITATIONS: [u32; MAX_LEVEL + 1] = [0, 2, 4, 6, 8]; /// blockages this credential is allowed to have recorded in order to /// advance from level i to level i+1. Again the LEVEL_INVITATIONS\[0\] /// entry is a dummy, as for LEVEL_INTERVAL. +// If you change this to have a number greater than 7, you need to add +// one or more bits to the ZKP. pub const MAX_BLOCKAGES: [u32; MAX_LEVEL + 1] = [0, 4, 3, 2, 2]; pub struct Request { @@ -399,7 +401,7 @@ pub fn request( // Encrypt the other blinded fields (times B) to D as well let ebucket = Scalar::random(&mut rng); let EncBucket = (&ebucket * Btable, &lox_cred.bucket * Btable + ebucket * D); - let newinvites: Scalar = LEVEL_INVITATIONS[new_level as usize].into(); + let newinvites: Scalar = LEVEL_INVITATIONS[trust_level as usize].into(); let eblockages = Scalar::random(&mut rng); let EncBlockages = ( &eblockages * Btable, @@ -830,7 +832,7 @@ impl BridgeAuth { // Create the invitations_remaining attribute (Scalar), which is // the number of invitations at the new level - let invitations_remaining: Scalar = LEVEL_INVITATIONS[new_level].into(); + let invitations_remaining: Scalar = LEVEL_INVITATIONS[level].into(); // Compute the MAC on the visible attributes let b = Scalar::random(&mut rng); diff --git a/crates/lox-library/src/tests.rs b/crates/lox-library/src/tests.rs index d8ef0fc..38d23cc 100644 --- a/crates/lox-library/src/tests.rs +++ b/crates/lox-library/src/tests.rs @@ -200,3 +200,35 @@ fn test_level_up() { println!("cred4 = {:?}", cred4); assert!(ba.verify_lox(&cred4)); } + +#[test] +fn test_issue_inv() { + 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)); + + // 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(); +}