Procházet zdrojové kódy

The complete migration protocol for the case of migrating from trust level 0 to trust level 1

Ian Goldberg před 3 roky
rodič
revize
8bb355621d
3 změnil soubory, kde provedl 667 přidání a 6 odebrání
  1. 21 3
      src/lib.rs
  2. 612 0
      src/migration.rs
  3. 34 3
      src/tests.rs

+ 21 - 3
src/lib.rs

@@ -21,7 +21,6 @@ pub mod bridge_table;
 pub mod cred;
 pub mod dup_filter;
 pub mod migration_table;
-pub mod trust_promotion;
 
 use sha2::Sha512;
 
@@ -33,6 +32,8 @@ use curve25519_dalek::constants as dalek_constants;
 use curve25519_dalek::ristretto::RistrettoBasepointTable;
 use curve25519_dalek::ristretto::RistrettoPoint;
 use curve25519_dalek::scalar::Scalar;
+#[cfg(test)]
+use curve25519_dalek::traits::IsIdentity;
 
 use ed25519_dalek::{Keypair, PublicKey, Signature, SignatureError, Signer, Verifier};
 use subtle::ConstantTimeEq;
@@ -257,8 +258,12 @@ impl BridgeAuth {
     }
 
     #[cfg(test)]
-    /// Verify the MAC on a Lox credential
+    /// Verify the two MACs on a Lox credential
     pub fn verify_lox(&self, cred: &cred::Lox) -> bool {
+        if cred.P.is_identity() || cred.P_noopmigration.is_identity() {
+            return false;
+        }
+
         let Q = (self.lox_priv.x[0]
             + cred.id * self.lox_priv.x[1]
             + cred.bucket * self.lox_priv.x[2]
@@ -267,12 +272,23 @@ impl BridgeAuth {
             + cred.invites_remaining * self.lox_priv.x[5]
             + cred.invites_issued * self.lox_priv.x[6])
             * cred.P;
-        return Q == cred.Q;
+
+        let Q_noopmigration = (self.migration_priv.x[0]
+            + cred.id * self.migration_priv.x[1]
+            + cred.bucket * self.migration_priv.x[2]
+            + cred.bucket * self.migration_priv.x[3])
+            * cred.P_noopmigration;
+
+        return Q == cred.Q && Q_noopmigration == cred.Q_noopmigration;
     }
 
     #[cfg(test)]
     /// Verify the MAC on a Migration credential
     pub fn verify_migration(&self, cred: &cred::Migration) -> bool {
+        if cred.P.is_identity() {
+            return false;
+        }
+
         let Q = (self.migration_priv.x[0]
             + cred.lox_id * self.migration_priv.x[1]
             + cred.from_bucket * self.migration_priv.x[2]
@@ -303,7 +319,9 @@ pub fn pt_dbl(P: &RistrettoPoint) -> RistrettoPoint {
 }
 
 // The protocol modules
+pub mod migration;
 pub mod open_invite;
+pub mod trust_promotion;
 
 // Unit tests
 #[cfg(test)]

+ 612 - 0
src/migration.rs

@@ -0,0 +1,612 @@
+/*! A module for the protocol for the user to migrate from one bucket to
+another (and possibly also change trust level).
+
+For the case of migrating from trust level 0 (a one-bridge bucket) to
+trust level 1 (a three-bridge bucket), the user presents their current
+Lox credential:
+
+- id: revealed
+- bucket: blinded
+- trust_level: revealed to be 0
+- level_since: blinded
+- invites_remaining: revealed to be 0
+- invites_issued: revealed to be 0
+
+and a Migration credential:
+
+- id: revealed as the same as the Lox credential id above
+- from_bucket: blinded, but proved in ZK that it's the same as the
+  bucket in the Lox credential above
+- to_bucket: 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 the to_bucket
+  in the Migration credential above
+- trust_level: 1
+- level_since: today
+- invites_remaining: 0
+- invites_issued: 0
+
+*/
+
+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::cred;
+use super::{BridgeAuth, IssuerPubKey};
+use super::{CMZ_A, CMZ_A_TABLE, CMZ_B, CMZ_B_TABLE};
+
+pub struct Request {
+    // Fields for blind showing the Lox credential
+    // We don't need to include invites_remaining or invites_issued,
+    // since they must be 0
+    P_lox: RistrettoPoint,
+    id: Scalar,
+    CBucket: RistrettoPoint,
+    trust_level: Scalar,
+    CSince: RistrettoPoint,
+    CQ_lox: RistrettoPoint,
+
+    // Fields for blind showing the Migration credential
+    P_mig: RistrettoPoint,
+    CFromBucket: RistrettoPoint,
+    CToBucket: RistrettoPoint,
+    CQ_mig: RistrettoPoint,
+
+    // Fields for user blinding of the Lox credential to be issued
+    D: RistrettoPoint,
+    EncIdClient: (RistrettoPoint, RistrettoPoint),
+    EncBucket: (RistrettoPoint, RistrettoPoint),
+
+    // The combined ZKP
+    piUser: CompactProof,
+}
+
+#[derive(Debug)]
+pub struct State {
+    d: Scalar,
+    D: RistrettoPoint,
+    EncIdClient: (RistrettoPoint, RistrettoPoint),
+    EncBucket: (RistrettoPoint, RistrettoPoint),
+    id_client: Scalar,
+    to_bucket: Scalar,
+}
+
+pub struct Response {
+    // The new attributes; trust_level = 1 is implicit
+    level_since: Scalar,
+
+    // The fields for the new Lox credential
+    P: RistrettoPoint,
+    EncQ: (RistrettoPoint, RistrettoPoint),
+    id_server: Scalar,
+    TId: RistrettoPoint,
+    TBucket: RistrettoPoint,
+
+    // The fields for the implicit noop migration ("nm") credential
+    P_nm: RistrettoPoint,
+    EncQ_nm: (RistrettoPoint, RistrettoPoint),
+    TId_nm: RistrettoPoint,
+    TBucket_nm: RistrettoPoint,
+
+    // The ZKP
+    piBlindIssue: CompactProof,
+}
+
+define_proof! {
+    requestproof,
+    "Migration Request",
+    (bucket, since, zbucket, zsince, negzQ_lox,
+     tobucket, zfrombucket, ztobucket, negzQ_mig,
+     d, eid_client, ebucket, id_client),
+    (P_lox, CBucket, CSince, V_lox, Xbucket, Xsince,
+     P_mig, CFromBucket, CToBucket, V_mig, Xfrombucket, Xtobucket,
+     D, EncIdClient0, EncIdClient1, EncBucket0, EncBucket1),
+    (A, B):
+    // Blind showing of the Lox credential
+    CBucket = (bucket*P_lox + zbucket*A),
+    CSince = (since*P_lox + zsince*A),
+    V_lox = (zbucket*Xbucket + zsince*Xsince + negzQ_lox*A),
+    // Blind showing of the Migration credential; note the use of the
+    // same "bucket" secret variable
+    CFromBucket = (bucket*P_mig + zfrombucket*A),
+    CToBucket = (tobucket*P_mig + ztobucket*A),
+    V_mig = (zfrombucket*Xfrombucket + ztobucket*Xtobucket + negzQ_mig*A),
+    // User blinding of the Lox credential to be issued; note the use of
+    // the same "tobucket" secret variable
+    EncIdClient0 = (eid_client*B),
+    EncIdClient1 = (id_client*B + eid_client*D),
+    EncBucket0 = (ebucket*B),
+    EncBucket1 = (tobucket*B + ebucket*D),
+    D = (d*B)
+}
+
+define_proof! {
+    blindissue,
+    "Migration Blind Issuing",
+    (x0, x0tilde, xid, xbucket, xlevel, xsince, s, b, tid, tbucket,
+     x0_nm, x0tilde_nm, xid_nm, xfrom_nm, xto_nm, s_nm, b_nm, tid_nm, tbucket_nm),
+    (P, EncQ0, EncQ1, X0, Xid, Xbucket, Xlevel, Xsince, Plevel, Psince, TId, TBucket,
+     P_nm, EncQ0_nm, EncQ1_nm, X0_nm, Xid_nm, Xfrom_nm, Xto_nm,
+     TId_nm, TBucket_nm,
+     D, EncId0, EncId1, EncBucket0, EncBucket1),
+    (A, B):
+    Xid = (xid*A),
+    Xlevel = (xlevel*A),
+    Xbucket = (xbucket*A),
+    Xsince = (xsince*A),
+    X0 = (x0*B + x0tilde*A),
+    P = (b*B),
+    TId = (b*Xid),
+    TId = (tid*A),
+    TBucket = (b*Xbucket),
+    TBucket = (tbucket*A),
+    EncQ0 = (s*B + tid*EncId0 + tbucket*EncBucket0),
+    EncQ1 = (s*D + tid*EncId1 + tbucket*EncBucket1 + x0*P + xlevel*Plevel + xsince*Psince),
+    Xid_nm = (xid_nm*A),
+    Xfrom_nm = (xfrom_nm*A),
+    Xto_nm = (xto_nm*A),
+    X0_nm = (x0_nm*B + x0tilde_nm*A),
+    P_nm = (b_nm*B),
+    TId_nm = (b_nm*Xid_nm),
+    TId_nm = (tid_nm*A),
+    TBucket_nm = (b_nm*Xfrom_nm + b_nm*Xto_nm),
+    TBucket_nm = (tbucket_nm*A),
+    EncQ0_nm = (s_nm*B + tid_nm*EncId0 + tbucket_nm*EncBucket0),
+    EncQ1_nm = (s_nm*D + tid_nm*EncId1 + tbucket_nm*EncBucket1 + x0_nm*P_nm)
+}
+
+pub fn request(
+    lox_cred: &cred::Lox,
+    migration_cred: &cred::Migration,
+    lox_pub: &IssuerPubKey,
+    migration_pub: &IssuerPubKey,
+) -> 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 that the credenials can be correctly shown; that is, the
+    // ids match and the Lox credential bucket matches the Migration
+    // credential from_bucket
+    if lox_cred.id != migration_cred.lox_id || lox_cred.bucket != migration_cred.from_bucket {
+        return Err(ProofError::VerificationFailure);
+    }
+
+    // We only support migrating from trust level 0 to trust level 1
+    // right now
+    if lox_cred.trust_level != Scalar::zero() {
+        return Err(ProofError::VerificationFailure);
+    }
+
+    // Blind showing the Lox credential
+
+    // Reblind P and Q
+    let mut rng = rand::thread_rng();
+    let t_lox = Scalar::random(&mut rng);
+    let P_lox = t_lox * lox_cred.P;
+    let Q_lox = t_lox * lox_cred.Q;
+
+    // Form Pedersen commitments to the blinded attributes
+    let zbucket = Scalar::random(&mut rng);
+    let zsince = Scalar::random(&mut rng);
+    let CBucket = lox_cred.bucket * P_lox + &zbucket * Atable;
+    let CSince = lox_cred.level_since * P_lox + &zsince * 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_lox = Scalar::random(&mut rng);
+    let CQ_lox = Q_lox - &negzQ_lox * Atable;
+
+    // Compute the "error factor"
+    let V_lox = zbucket * lox_pub.X[2] + zsince * lox_pub.X[4] + &negzQ_lox * Atable;
+
+    // Blind showing the Migration credential
+
+    // Reblind P and Q
+    let t_mig = Scalar::random(&mut rng);
+    let P_mig = t_mig * migration_cred.P;
+    let Q_mig = t_mig * migration_cred.Q;
+
+    // Form Pedersen commitments to the blinded attributes
+    let zfrombucket = Scalar::random(&mut rng);
+    let ztobucket = Scalar::random(&mut rng);
+    let CFromBucket = migration_cred.from_bucket * P_mig + &zfrombucket * Atable;
+    let CToBucket = migration_cred.to_bucket * P_mig + &ztobucket * 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_mig = Scalar::random(&mut rng);
+    let CQ_mig = Q_mig - &negzQ_mig * Atable;
+
+    // Compute the "error factor"
+    let V_mig =
+        zfrombucket * migration_pub.X[2] + ztobucket * migration_pub.X[3] + &negzQ_mig * 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 bucket field (times B) to D as well
+    let ebucket = Scalar::random(&mut rng);
+    let EncBucket = (
+        &ebucket * Btable,
+        &migration_cred.to_bucket * Btable + ebucket * D,
+    );
+
+    // Construct the proof
+    let mut transcript = Transcript::new(b"migration request");
+    let piUser = requestproof::prove_compact(
+        &mut transcript,
+        requestproof::ProveAssignments {
+            A: &A,
+            B: &B,
+            P_lox: &P_lox,
+            CBucket: &CBucket,
+            CSince: &CSince,
+            V_lox: &V_lox,
+            Xbucket: &lox_pub.X[2],
+            Xsince: &lox_pub.X[4],
+            P_mig: &P_mig,
+            CFromBucket: &CFromBucket,
+            CToBucket: &CToBucket,
+            V_mig: &V_mig,
+            Xfrombucket: &migration_pub.X[2],
+            Xtobucket: &migration_pub.X[3],
+            D: &D,
+            EncIdClient0: &EncIdClient.0,
+            EncIdClient1: &EncIdClient.1,
+            EncBucket0: &EncBucket.0,
+            EncBucket1: &EncBucket.1,
+            bucket: &lox_cred.bucket,
+            since: &lox_cred.level_since,
+            zbucket: &zbucket,
+            zsince: &zsince,
+            negzQ_lox: &negzQ_lox,
+            tobucket: &migration_cred.to_bucket,
+            zfrombucket: &zfrombucket,
+            ztobucket: &ztobucket,
+            negzQ_mig: &negzQ_mig,
+            d: &d,
+            eid_client: &eid_client,
+            ebucket: &ebucket,
+            id_client: &id_client,
+        },
+    )
+    .0;
+
+    Ok((
+        Request {
+            P_lox,
+            id: lox_cred.id,
+            CBucket,
+            trust_level: lox_cred.trust_level,
+            CSince,
+            CQ_lox,
+            P_mig,
+            CFromBucket,
+            CToBucket,
+            CQ_mig,
+            D,
+            EncIdClient,
+            EncBucket,
+            piUser,
+        },
+        State {
+            d,
+            D,
+            EncIdClient,
+            EncBucket,
+            id_client,
+            to_bucket: migration_cred.to_bucket,
+        },
+    ))
+}
+
+impl BridgeAuth {
+    /// Receive a migration request
+    pub fn handle_migration(&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_lox.is_identity() || req.P_mig.is_identity() {
+            return Err(ProofError::VerificationFailure);
+        }
+
+        // We only currently support migrating from trust level 0
+        if req.trust_level != Scalar::zero() {
+            return Err(ProofError::VerificationFailure);
+        }
+
+        // Recompute the "error factors" using knowledge of our own
+        // (the issuer's) private key instead of knowledge of the
+        // hidden attributes
+        let Vprime_lox = (self.lox_priv.x[0]
+            + self.lox_priv.x[1] * req.id
+            + self.lox_priv.x[3] * req.trust_level)
+            * req.P_lox
+            + self.lox_priv.x[2] * req.CBucket
+            + self.lox_priv.x[4] * req.CSince
+            - req.CQ_lox;
+
+        let Vprime_mig = (self.migration_priv.x[0] + self.migration_priv.x[1] * req.id) * req.P_mig
+            + self.migration_priv.x[2] * req.CFromBucket
+            + self.migration_priv.x[3] * req.CToBucket
+            - req.CQ_mig;
+
+        // Verify the ZKP
+        let mut transcript = Transcript::new(b"migration request");
+        requestproof::verify_compact(
+            &req.piUser,
+            &mut transcript,
+            requestproof::VerifyAssignments {
+                A: &A.compress(),
+                B: &B.compress(),
+                P_lox: &req.P_lox.compress(),
+                CBucket: &req.CBucket.compress(),
+                CSince: &req.CSince.compress(),
+                V_lox: &Vprime_lox.compress(),
+                Xbucket: &self.lox_pub.X[2].compress(),
+                Xsince: &self.lox_pub.X[4].compress(),
+                P_mig: &req.P_mig.compress(),
+                CFromBucket: &req.CFromBucket.compress(),
+                CToBucket: &req.CToBucket.compress(),
+                V_mig: &Vprime_mig.compress(),
+                Xfrombucket: &self.migration_pub.X[2].compress(),
+                Xtobucket: &self.migration_pub.X[3].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(),
+            },
+        )?;
+
+        // 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);
+
+        // Create the trust_level attrubute (Scalar), which will be
+        // level 1
+        let trust_level: Scalar = Scalar::one();
+
+        // Create the level_since attribute (Scalar), which is today's
+        // Julian date
+        let level_since: Scalar = self.today().into();
+
+        // The invitations_remaining and invitations_issued attributes
+        // are 0 for level 0 and level 1 Lox credentials, so we don't
+        // need to explicitly create them.
+
+        // Compute the MAC on the visible attributes
+        let b = Scalar::random(&mut rng);
+        let P = &b * Btable;
+        // invites_remaining = invites_issued = 0
+        let QHc = (self.lox_priv.x[0]
+            + self.lox_priv.x[3] * trust_level
+            + self.lox_priv.x[4] * level_since)
+            * 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 id attribute
+        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 EncQ = (
+            EncQHc.0 + EncQId.0 + EncQBucket.0,
+            EncQHc.1 + EncQId.1 + EncQBucket.1,
+        );
+
+        // Now the no-op migration credential
+        // Compute the MAC on the visible attributes (none here)
+        let b_nm = Scalar::random(&mut rng);
+        let P_nm = &b_nm * Btable;
+        let QHc_nm = (self.migration_priv.x[0]) * P_nm;
+
+        // El Gamal encrypt it to the public key req.D
+        let s_nm = Scalar::random(&mut rng);
+        let EncQHc_nm = (&s_nm * Btable, QHc_nm + s_nm * req.D);
+
+        // Homomorphically compute the part of the MAC corresponding to
+        // the blinded attributes
+        let tid_nm = self.migration_priv.x[1] * b_nm;
+        let TId_nm = &tid_nm * Atable;
+        let EncQId_nm = (tid_nm * EncId.0, tid_nm * EncId.1);
+        let tbucket_nm = (self.migration_priv.x[2] + self.migration_priv.x[3]) * b_nm;
+        let TBucket_nm = &tbucket_nm * Atable;
+        let EncQBucket_nm = (tbucket_nm * req.EncBucket.0, tbucket_nm * req.EncBucket.1);
+
+        let EncQ_nm = (
+            EncQHc_nm.0 + EncQId_nm.0 + EncQBucket_nm.0,
+            EncQHc_nm.1 + EncQId_nm.1 + EncQBucket_nm.1,
+        );
+
+        let mut transcript = Transcript::new(b"migration 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],
+                Plevel: &(trust_level * P),
+                Psince: &(level_since * P),
+                TId: &TId,
+                TBucket: &TBucket,
+                P_nm: &P_nm,
+                EncQ0_nm: &EncQ_nm.0,
+                EncQ1_nm: &EncQ_nm.1,
+                X0_nm: &self.migration_pub.X[0],
+                Xid_nm: &self.migration_pub.X[1],
+                Xfrom_nm: &self.migration_pub.X[2],
+                Xto_nm: &self.migration_pub.X[3],
+                TId_nm: &TId_nm,
+                TBucket_nm: &TBucket_nm,
+                D: &req.D,
+                EncId0: &EncId.0,
+                EncId1: &EncId.1,
+                EncBucket0: &req.EncBucket.0,
+                EncBucket1: &req.EncBucket.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],
+                s: &s,
+                b: &b,
+                tid: &tid,
+                tbucket: &tbucket,
+                x0_nm: &self.migration_priv.x[0],
+                x0tilde_nm: &self.migration_priv.x0tilde,
+                xid_nm: &self.migration_priv.x[1],
+                xfrom_nm: &self.migration_priv.x[2],
+                xto_nm: &self.migration_priv.x[3],
+                s_nm: &s_nm,
+                b_nm: &b_nm,
+                tid_nm: &tid_nm,
+                tbucket_nm: &tbucket_nm,
+            },
+        )
+        .0;
+
+        Ok(Response {
+            level_since,
+            P,
+            EncQ,
+            id_server,
+            TId,
+            TBucket,
+            P_nm,
+            EncQ_nm,
+            TId_nm,
+            TBucket_nm,
+            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,
+    migration_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() || resp.P_nm.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"migration 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(),
+            // The new trust level is 1
+            Plevel: &(Scalar::one() * resp.P).compress(),
+            Psince: &(resp.level_since * resp.P).compress(),
+            TId: &resp.TId.compress(),
+            TBucket: &resp.TBucket.compress(),
+            P_nm: &resp.P_nm.compress(),
+            EncQ0_nm: &resp.EncQ_nm.0.compress(),
+            EncQ1_nm: &resp.EncQ_nm.1.compress(),
+            X0_nm: &migration_pub.X[0].compress(),
+            Xid_nm: &migration_pub.X[1].compress(),
+            Xfrom_nm: &migration_pub.X[2].compress(),
+            Xto_nm: &migration_pub.X[3].compress(),
+            TId_nm: &resp.TId_nm.compress(),
+            TBucket_nm: &resp.TBucket_nm.compress(),
+            D: &state.D.compress(),
+            EncId0: &EncId.0.compress(),
+            EncId1: &EncId.1.compress(),
+            EncBucket0: &state.EncBucket.0.compress(),
+            EncBucket1: &state.EncBucket.1.compress(),
+        },
+    )?;
+
+    // Decrypt EncQ
+    let Q = resp.EncQ.1 - (state.d * resp.EncQ.0);
+
+    // Decrypt EncQ_nm
+    let Q_nm = resp.EncQ_nm.1 - (state.d * resp.EncQ_nm.0);
+
+    Ok(cred::Lox {
+        P: resp.P,
+        Q,
+        id,
+        bucket: state.to_bucket,
+        trust_level: Scalar::one(),
+        level_since: resp.level_since,
+        invites_remaining: Scalar::zero(),
+        invites_issued: Scalar::zero(),
+        P_noopmigration: resp.P_nm,
+        Q_noopmigration: Q_nm,
+    })
+}

+ 34 - 3
src/tests.rs

@@ -45,8 +45,7 @@ fn test_open_invite() {
     assert!(ba.verify_lox(&cred));
 }
 
-#[test]
-fn test_trust_promotion() {
+fn setup() -> (BridgeDb, BridgeAuth) {
     // Create a BridegDb
     let bdb = BridgeDb::new(15);
     // Create a BridgeAuth
@@ -76,6 +75,10 @@ fn test_trust_promotion() {
     // Create the encrypted bridge table
     ba.bridge_table.encrypt_table();
 
+    (bdb, ba)
+}
+
+fn trust_promotion(bdb: &BridgeDb, ba: &mut BridgeAuth) -> (cred::Lox, cred::Migration) {
     // Issue an open invitation
     let inv = bdb.invite();
 
@@ -91,7 +94,16 @@ fn test_trust_promotion() {
     let (promreq, promstate) = trust_promotion::request(&cred, &ba.lox_pub, ba.today()).unwrap();
     let promresp = ba.handle_trust_promotion(promreq).unwrap();
     let migcred = trust_promotion::handle_response(promstate, promresp).unwrap();
-    println!("resp = {:?}", migcred);
+
+    (cred, migcred)
+}
+
+#[test]
+fn test_trust_promotion() {
+    let (bdb, mut ba) = setup();
+
+    let (_loxcred, migcred) = trust_promotion(&bdb, &mut ba);
+
     assert!(ba.verify_migration(&migcred));
     // Check that we can use the to_bucket in the Migration credenital
     // to read a bucket
@@ -99,3 +111,22 @@ fn test_trust_promotion() {
     let bucket = ba.bridge_table.decrypt_bucket_id(id, &key).unwrap();
     println!("bucket = {:?}", bucket);
 }
+
+#[test]
+fn test_level0_migration() {
+    let (bdb, mut ba) = setup();
+
+    let (loxcred, migcred) = trust_promotion(&bdb, &mut ba);
+
+    let (migreq, migstate) =
+        migration::request(&loxcred, &migcred, &ba.lox_pub, &ba.migration_pub).unwrap();
+    let migresp = ba.handle_migration(migreq).unwrap();
+    let newloxcred =
+        migration::handle_response(migstate, migresp, &ba.lox_pub, &ba.migration_pub).unwrap();
+    assert!(ba.verify_lox(&newloxcred));
+    println!("newloxcred = {:?}", newloxcred);
+    // Check that we can use the credenital to read a bucket
+    let (id, key) = bridge_table::from_scalar(newloxcred.bucket).unwrap();
+    let bucket = ba.bridge_table.decrypt_bucket_id(id, &key).unwrap();
+    println!("bucket = {:?}", bucket);
+}