The request message of the issue invitation protocol

This commit is contained in:
Ian Goldberg 2021-05-03 19:05:42 -04:00
parent 654208769f
commit d924b98060
5 changed files with 523 additions and 2 deletions

View File

@ -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,
}

View File

@ -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<Response, ProofError>.
pub mod proto {
pub mod issue_invite;
pub mod level_up;
pub mod migration;
pub mod open_invite;

View File

@ -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,
},
))
}

View File

@ -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);

View File

@ -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();
}