Complete the redeem invitation protocol

This commit is contained in:
Ian Goldberg 2021-05-04 17:48:15 -04:00
parent dd505ccfd7
commit b0290f5480
5 changed files with 282 additions and 10 deletions

View File

@ -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"

View File

@ -206,8 +206,10 @@ pub struct BridgeAuth {
/// Duplicate filter for open invitations
openinv_filter: dup_filter::DupFilter<Scalar>,
/// Duplicate filter for credential ids
/// Duplicate filter for Lox credential ids
id_filter: dup_filter::DupFilter<Scalar>,
/// Duplicate filter for Invitation credential ids
inv_id_filter: dup_filter::DupFilter<Scalar>,
/// Duplicate filter for trust promotions (from untrusted level 0 to
/// trusted level 1)
trust_promotion_filter: dup_filter::DupFilter<Scalar>,
@ -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(),
}

View File

@ -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(

View File

@ -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<Response, ProofError> {
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<cred::Lox, ProofError> {
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,
})
}

View File

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