open_invite.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. /*! A module for the protocol for the user to redeem an open invitation
  2. with the BA (bridge authority) to receive their initial Lox
  3. credential. The credential will have attributes:
  4. - id: jointly chosen by the user and BA
  5. - bucket: set by the BA
  6. - trust_level: 0
  7. - level_since: today
  8. - invites_remaining: 0
  9. - invites_issued: 0
  10. */
  11. use curve25519_dalek::ristretto::RistrettoBasepointTable;
  12. use curve25519_dalek::ristretto::RistrettoPoint;
  13. use curve25519_dalek::scalar::Scalar;
  14. use curve25519_dalek::traits::IsIdentity;
  15. use zkp::CompactProof;
  16. use zkp::ProofError;
  17. use zkp::Transcript;
  18. use super::bridge_table;
  19. use super::cred;
  20. use super::dup_filter::SeenType;
  21. use super::{BridgeAuth, IssuerPubKey};
  22. use super::{CMZ_A, CMZ_A_TABLE, CMZ_B, CMZ_B_TABLE};
  23. /// The request message for this protocol
  24. pub struct Request {
  25. invite: [u8; super::OPENINV_LENGTH],
  26. D: RistrettoPoint,
  27. EncIdClient: (RistrettoPoint, RistrettoPoint),
  28. piUserBlinding: CompactProof,
  29. }
  30. #[derive(Debug)]
  31. /// The client state for this protocol
  32. pub struct State {
  33. d: Scalar,
  34. D: RistrettoPoint,
  35. EncIdClient: (RistrettoPoint, RistrettoPoint),
  36. id_client: Scalar,
  37. }
  38. /// The response message for this protocol
  39. pub struct Response {
  40. P: RistrettoPoint,
  41. EncQ: (RistrettoPoint, RistrettoPoint),
  42. id_server: Scalar,
  43. TId: RistrettoPoint,
  44. bucket: Scalar,
  45. level_since: Scalar,
  46. P_noopmigration: RistrettoPoint,
  47. EncQ_noopmigration: (RistrettoPoint, RistrettoPoint),
  48. TId_noopmigration: RistrettoPoint,
  49. piBlindIssue: CompactProof,
  50. }
  51. // The userblinding ZKP
  52. define_proof! {
  53. userblinding,
  54. "Open Invitation User Blinding",
  55. (d, eid_client, id_client),
  56. (EncIdClient0, EncIdClient1, D),
  57. (B) :
  58. EncIdClient0 = (eid_client*B),
  59. EncIdClient1 = (id_client*B + eid_client*D),
  60. D = (d*B)
  61. }
  62. // The issuing ZKP
  63. define_proof! {
  64. blindissue,
  65. "Open Invitation Blind Issuing",
  66. (x0, x0tilde, xid, xbucket, xsince, s, b, tid,
  67. x0_nm, x0tilde_nm, xid_nm, xfrom_nm, xto_nm, s_nm, b_nm, tid_nm),
  68. (P, EncQ0, EncQ1, X0, Xid, Xbucket, Xsince, Pbucket, Psince, TId,
  69. P_nm, EncQ0_nm, EncQ1_nm, X0_nm, Xid_nm, Xfrom_nm, Xto_nm,
  70. Pbucket_nm, TId_nm,
  71. D, EncId0, EncId1),
  72. (A, B) :
  73. Xid = (xid*A),
  74. Xbucket = (xbucket*A),
  75. Xsince = (xsince*A),
  76. X0 = (x0*B + x0tilde*A),
  77. P = (b*B),
  78. TId = (b*Xid),
  79. TId = (tid*A),
  80. EncQ0 = (s*B + tid*EncId0),
  81. EncQ1 = (s*D + tid*EncId1 + x0*P + xbucket*Pbucket + xsince*Psince),
  82. Xid_nm = (xid_nm*A),
  83. Xfrom_nm = (xfrom_nm*A),
  84. Xto_nm = (xto_nm*A),
  85. X0_nm = (x0_nm*B + x0tilde_nm*A),
  86. P_nm = (b_nm*B),
  87. TId_nm = (b_nm*Xid_nm),
  88. TId_nm = (tid_nm*A),
  89. EncQ0_nm = (s_nm*B + tid_nm*EncId0),
  90. EncQ1_nm = (s_nm*D + tid_nm*EncId1 + x0_nm*P_nm + xfrom_nm*Pbucket_nm + xto_nm*Pbucket_nm)
  91. }
  92. /// Submit an open invitation issued by the BridgeDb to receive your
  93. /// first Lox credential
  94. pub fn request(invite: &[u8; super::OPENINV_LENGTH]) -> (Request, State) {
  95. let B: &RistrettoPoint = &CMZ_B;
  96. let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
  97. // Pick an ElGamal keypair
  98. let mut rng = rand::thread_rng();
  99. let d = Scalar::random(&mut rng);
  100. let D = &d * Btable;
  101. // Pick a random client component of the id
  102. let id_client = Scalar::random(&mut rng);
  103. // Encrypt it (times the basepoint B) to the ElGamal public key D we
  104. // just created
  105. let eid_client = Scalar::random(&mut rng);
  106. let EncIdClient = (&eid_client * Btable, &id_client * Btable + eid_client * D);
  107. // Construct the proof of correct user blinding
  108. let mut transcript = Transcript::new(b"open invite user blinding");
  109. let piUserBlinding = userblinding::prove_compact(
  110. &mut transcript,
  111. userblinding::ProveAssignments {
  112. B: &B,
  113. EncIdClient0: &EncIdClient.0,
  114. EncIdClient1: &EncIdClient.1,
  115. D: &D,
  116. d: &d,
  117. eid_client: &eid_client,
  118. id_client: &id_client,
  119. },
  120. )
  121. .0;
  122. (
  123. Request {
  124. invite: *invite,
  125. D,
  126. EncIdClient,
  127. piUserBlinding,
  128. },
  129. State {
  130. d,
  131. D,
  132. EncIdClient,
  133. id_client,
  134. },
  135. )
  136. }
  137. impl BridgeAuth {
  138. /// Receive an open invitation issued by the BridgeDb and if it is
  139. /// valid and fresh, issue a Lox credential at trust level 0.
  140. pub fn handle_open_invite(&mut self, req: Request) -> Result<Response, ProofError> {
  141. // Check the signature on the open_invite. We manually match
  142. // here because we're changing the Err type from SignatureError
  143. // to ProofError
  144. let (invite_id, bucket_id_u32) =
  145. match super::BridgeDb::verify(req.invite, self.bridgedb_pub) {
  146. Ok(res) => res,
  147. Err(_) => return Err(ProofError::VerificationFailure),
  148. };
  149. let bucket_id: usize = bucket_id_u32 as usize;
  150. // Only proceed if the invite_id is fresh
  151. if self.openinv_filter.filter(&invite_id) == SeenType::Seen {
  152. return Err(ProofError::VerificationFailure);
  153. }
  154. // And also check that the bucket id is valid
  155. if bucket_id >= self.bridge_table.num_buckets() {
  156. return Err(ProofError::VerificationFailure);
  157. }
  158. let A: &RistrettoPoint = &CMZ_A;
  159. let B: &RistrettoPoint = &CMZ_B;
  160. let Atable: &RistrettoBasepointTable = &CMZ_A_TABLE;
  161. let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
  162. // Next check the proof in the request
  163. let mut transcript = Transcript::new(b"open invite user blinding");
  164. userblinding::verify_compact(
  165. &req.piUserBlinding,
  166. &mut transcript,
  167. userblinding::VerifyAssignments {
  168. B: &B.compress(),
  169. EncIdClient0: &req.EncIdClient.0.compress(),
  170. EncIdClient1: &req.EncIdClient.1.compress(),
  171. D: &req.D.compress(),
  172. },
  173. )?;
  174. // Choose a random server id component to add to the client's
  175. // (blinded) id component
  176. let mut rng = rand::thread_rng();
  177. let id_server = Scalar::random(&mut rng);
  178. let EncId = (req.EncIdClient.0, req.EncIdClient.1 + &id_server * Btable);
  179. // Create the bucket attribute (Scalar), which is a combination
  180. // of the bucket id (u32) and the bucket's decryption key ([u8; 16])
  181. let bucket_key = self.bridge_table.keys[bucket_id];
  182. let bucket: Scalar = bridge_table::to_scalar(bucket_id_u32, bucket_key);
  183. // Create the level_since attribute (Scalar), which is today's
  184. // Julian date
  185. let level_since: Scalar = self.today().into();
  186. // Compute the MAC on the visible attributes
  187. let b = Scalar::random(&mut rng);
  188. let P = &b * Btable;
  189. // trust_level = invites_remaining = invites_issued = 0
  190. let QHc =
  191. (self.lox_priv.x[0] + self.lox_priv.x[2] * bucket + self.lox_priv.x[4] * level_since)
  192. * P;
  193. // El Gamal encrypt it to the public key req.D
  194. let s = Scalar::random(&mut rng);
  195. let EncQHc = (&s * Btable, QHc + s * req.D);
  196. // Homomorphically compute the part of the MAC corresponding to
  197. // the blinded id attribute
  198. let tid = self.lox_priv.x[1] * b;
  199. let TId = &tid * Atable;
  200. let EncQId = (tid * EncId.0, tid * EncId.1);
  201. let EncQ = (EncQHc.0 + EncQId.0, EncQHc.1 + EncQId.1);
  202. // Now the no-op migration credential
  203. // Compute the MAC on the visible attributes
  204. let b_noopmigration = Scalar::random(&mut rng);
  205. let P_noopmigration = &b_noopmigration * Btable;
  206. let QHc_noopmigration = (self.migration_priv.x[0]
  207. + self.migration_priv.x[2] * bucket
  208. + self.migration_priv.x[3] * bucket)
  209. * P_noopmigration;
  210. // El Gamal encrypt it to the public key req.D
  211. let s_noopmigration = Scalar::random(&mut rng);
  212. let EncQHc_noopmigration = (
  213. &s_noopmigration * Btable,
  214. QHc_noopmigration + s_noopmigration * req.D,
  215. );
  216. // Homomorphically compute the part of the MAC corresponding to
  217. // the blinded id attribute
  218. let tid_noopmigration = self.migration_priv.x[1] * b_noopmigration;
  219. let TId_noopmigration = &tid_noopmigration * Atable;
  220. let EncQId_noopmigration = (tid_noopmigration * EncId.0, tid_noopmigration * EncId.1);
  221. let EncQ_noopmigration = (
  222. EncQHc_noopmigration.0 + EncQId_noopmigration.0,
  223. EncQHc_noopmigration.1 + EncQId_noopmigration.1,
  224. );
  225. let mut transcript = Transcript::new(b"open invite issuing");
  226. let piBlindIssue = blindissue::prove_compact(
  227. &mut transcript,
  228. blindissue::ProveAssignments {
  229. A: &A,
  230. B: &B,
  231. P: &P,
  232. EncQ0: &EncQ.0,
  233. EncQ1: &EncQ.1,
  234. X0: &self.lox_pub.X[0],
  235. Xid: &self.lox_pub.X[1],
  236. Xbucket: &self.lox_pub.X[2],
  237. Xsince: &self.lox_pub.X[4],
  238. Pbucket: &(bucket * P),
  239. Psince: &(level_since * P),
  240. TId: &TId,
  241. P_nm: &P_noopmigration,
  242. EncQ0_nm: &EncQ_noopmigration.0,
  243. EncQ1_nm: &EncQ_noopmigration.1,
  244. X0_nm: &self.migration_pub.X[0],
  245. Xid_nm: &self.migration_pub.X[1],
  246. Xfrom_nm: &self.migration_pub.X[2],
  247. Xto_nm: &self.migration_pub.X[3],
  248. Pbucket_nm: &(bucket * P_noopmigration),
  249. TId_nm: &TId_noopmigration,
  250. D: &req.D,
  251. EncId0: &EncId.0,
  252. EncId1: &EncId.1,
  253. x0: &self.lox_priv.x[0],
  254. x0tilde: &self.lox_priv.x0tilde,
  255. xid: &self.lox_priv.x[1],
  256. xbucket: &self.lox_priv.x[2],
  257. xsince: &self.lox_priv.x[4],
  258. s: &s,
  259. b: &b,
  260. tid: &tid,
  261. x0_nm: &self.migration_priv.x[0],
  262. x0tilde_nm: &self.migration_priv.x0tilde,
  263. xid_nm: &self.migration_priv.x[1],
  264. xfrom_nm: &self.migration_priv.x[2],
  265. xto_nm: &self.migration_priv.x[3],
  266. s_nm: &s_noopmigration,
  267. b_nm: &b_noopmigration,
  268. tid_nm: &tid_noopmigration,
  269. },
  270. )
  271. .0;
  272. Ok(Response {
  273. P,
  274. EncQ,
  275. id_server,
  276. TId,
  277. bucket,
  278. level_since,
  279. P_noopmigration,
  280. EncQ_noopmigration,
  281. TId_noopmigration,
  282. piBlindIssue,
  283. })
  284. }
  285. }
  286. /// Handle the reponse to the request, producing the desired Lox
  287. /// credential if successful.
  288. pub fn handle_response(
  289. state: State,
  290. resp: Response,
  291. lox_pub: &IssuerPubKey,
  292. migration_pub: &IssuerPubKey,
  293. ) -> Result<cred::Lox, ProofError> {
  294. let A: &RistrettoPoint = &CMZ_A;
  295. let B: &RistrettoPoint = &CMZ_B;
  296. let Btable: &RistrettoBasepointTable = &CMZ_B_TABLE;
  297. if resp.P.is_identity() || resp.P_noopmigration.is_identity() {
  298. return Err(ProofError::VerificationFailure);
  299. }
  300. // Add the server's contribution to the id to our own, both in plain
  301. // and encrypted form
  302. let id = state.id_client + resp.id_server;
  303. let EncId = (
  304. state.EncIdClient.0,
  305. state.EncIdClient.1 + &resp.id_server * Btable,
  306. );
  307. // Verify the proof
  308. let mut transcript = Transcript::new(b"open invite issuing");
  309. blindissue::verify_compact(
  310. &resp.piBlindIssue,
  311. &mut transcript,
  312. blindissue::VerifyAssignments {
  313. A: &A.compress(),
  314. B: &B.compress(),
  315. P: &resp.P.compress(),
  316. EncQ0: &resp.EncQ.0.compress(),
  317. EncQ1: &resp.EncQ.1.compress(),
  318. X0: &lox_pub.X[0].compress(),
  319. Xid: &lox_pub.X[1].compress(),
  320. Xbucket: &lox_pub.X[2].compress(),
  321. Xsince: &lox_pub.X[4].compress(),
  322. Pbucket: &(resp.bucket * resp.P).compress(),
  323. Psince: &(resp.level_since * resp.P).compress(),
  324. TId: &resp.TId.compress(),
  325. P_nm: &resp.P_noopmigration.compress(),
  326. EncQ0_nm: &resp.EncQ_noopmigration.0.compress(),
  327. EncQ1_nm: &resp.EncQ_noopmigration.1.compress(),
  328. X0_nm: &migration_pub.X[0].compress(),
  329. Xid_nm: &migration_pub.X[1].compress(),
  330. Xfrom_nm: &migration_pub.X[2].compress(),
  331. Xto_nm: &migration_pub.X[3].compress(),
  332. Pbucket_nm: &(resp.bucket * resp.P_noopmigration).compress(),
  333. TId_nm: &resp.TId_noopmigration.compress(),
  334. D: &state.D.compress(),
  335. EncId0: &EncId.0.compress(),
  336. EncId1: &EncId.1.compress(),
  337. },
  338. )?;
  339. // Decrypt EncQ
  340. let Q = resp.EncQ.1 - (state.d * resp.EncQ.0);
  341. // Decrypt EncQ_noopmigration
  342. let Q_noopmigration = resp.EncQ_noopmigration.1 - (state.d * resp.EncQ_noopmigration.0);
  343. Ok(cred::Lox {
  344. P: resp.P,
  345. Q,
  346. id,
  347. bucket: resp.bucket,
  348. trust_level: Scalar::zero(),
  349. level_since: resp.level_since,
  350. invites_remaining: Scalar::zero(),
  351. invites_issued: Scalar::zero(),
  352. P_noopmigration: resp.P_noopmigration,
  353. Q_noopmigration,
  354. })
  355. }