level_up.rs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. /*! A module for the protocol for the user to increase their trust level
  2. (from a level at least 1; use the trust promotion protocol to go from
  3. untrusted (level 0) to minimally trusted (level 1).
  4. They are allowed to do this as long as some amount of time (depending on
  5. their current level) has elapsed since their last level change, and they
  6. have a Bucket Reachability credential for their current bucket and
  7. today's date. (Such credentials are placed daily in the encrypted
  8. bridge table.)
  9. The user presents their current Lox credential:
  10. - id: revealed
  11. - bucket: blinded
  12. - trust_level: revealed, and must be at least 1
  13. - level_since: blinded, but proved in ZK that it's at least the
  14. appropriate number of days ago
  15. - invites_remaining: blinded
  16. - blockages: blinded, but proved in ZK that it's at most the appropriate
  17. blockage limit for the target trust level
  18. and a Bucket Reachability credential:
  19. - date: revealed to be today
  20. - bucket: blinded, but proved in ZK that it's the same as in the Lox
  21. credential above
  22. and a new Lox credential to be issued:
  23. - id: jointly chosen by the user and BA
  24. - bucket: blinded, but proved in ZK that it's the same as in the Lox
  25. credential above
  26. - trust_level: revealed to be one more than the trust level above
  27. - level_since: today
  28. - invites_remaining: revealed to be the number of invites for the new
  29. level (note that the invites_remaining from the previous credential
  30. are _not_ carried over)
  31. - blockages: blinded, but proved in ZK that it's the same as in the
  32. Lox credential above
  33. */
  34. #[cfg(feature = "bridgeauth")]
  35. use super::super::dup_filter::SeenType;
  36. #[cfg(feature = "bridgeauth")]
  37. use super::super::BridgeAuth;
  38. use super::super::{scalar_u32, Scalar, G};
  39. use super::errors::CredentialError;
  40. use crate::lox_creds::{BucketReachability, Lox};
  41. use cmz::*;
  42. use group::Group;
  43. use rand::{CryptoRng, RngCore};
  44. use sha2::Sha512;
  45. const SESSION_ID: &[u8] = b"level_up";
  46. /// The maximum trust level in the system. A user can run this level
  47. /// upgrade protocol when they're already at the max level; they will
  48. /// get a fresh invites_remaining batch, and reset their level_since
  49. /// field to today's date, but will remain in the max level.
  50. pub const MAX_LEVEL: usize = 4;
  51. /// LEVEL_INTERVAL\[i\] for i >= 1 is the minimum number of days a user
  52. /// must be at trust level i before advancing to level i+1 (or as above,
  53. /// remain at level i if i == MAX_LEVEL). Note that the
  54. /// LEVEL_INTERVAL\[0\] entry is a dummy; the trust_promotion protocol
  55. /// is used instead of this one to move from level 0 to level 1.
  56. pub const LEVEL_INTERVAL: [u32; MAX_LEVEL + 1] = [0, 14, 28, 56, 84];
  57. /// LEVEL_INVITATIONS\[i\] for i >= 1 is the number of invitations a
  58. /// user will be eligible to issue upon advancing from level i to level
  59. /// i+1. Again the LEVEL_INVITATIONS\[0\] entry is a dummy, as for
  60. /// LEVEL_INTERVAL.
  61. pub const LEVEL_INVITATIONS: [u32; MAX_LEVEL + 1] = [0, 2, 4, 6, 8];
  62. /// MAX_BLOCKAGES\[i\] for i >= 1 is the maximum number of bucket
  63. /// blockages this credential is allowed to have recorded in order to
  64. /// advance from level i to level i+1. Again the LEVEL_INVITATIONS\[0\]
  65. /// entry is a dummy, as for LEVEL_INTERVAL.
  66. // If you change this to have a number greater than 7, you need to add
  67. // one or more bits to the ZKP.
  68. pub const MAX_BLOCKAGES: [u32; MAX_LEVEL + 1] = [0, 4, 3, 2, 2];
  69. muCMZProtocol! { level_up<credential_expiry, eligibility_max_age, max_blockage, today>,
  70. [ L: Lox { id: R, bucket: H, trust_level: R, level_since: H, invites_remaining: H, blockages: H },
  71. B: BucketReachability { date: R, bucket: H } ],
  72. N: Lox {id: J, bucket: H, trust_level: R, level_since: S, invites_remaining: I, blockages: H },
  73. (credential_expiry..=eligibility_max_age).contains(L.level_since),
  74. (0..=max_blockage).contains(L.blockages),
  75. B.date = today,
  76. B.bucket = L.bucket,
  77. N.bucket = L.bucket,
  78. N.trust_level = L.trust_level + 1,
  79. N.blockages = L.blockages,
  80. }
  81. pub fn request(
  82. rng: &mut (impl CryptoRng + RngCore),
  83. L: Lox,
  84. B: BucketReachability,
  85. pubkeys: CMZPubkey<G>,
  86. today: u32,
  87. ) -> Result<(level_up::Request, level_up::ClientState), CredentialError> {
  88. cmz_group_init(G::hash_from_bytes::<Sha512>(b"CMZ Generator A"));
  89. // Ensure the credential can be correctly shown: it must be the case
  90. // that level_since + LEVEL_INTERVAL[level] <= today.
  91. let level_since: u32 = match scalar_u32(&L.level_since.unwrap()) {
  92. Some(v) => v,
  93. None => {
  94. return Err(CredentialError::InvalidField(
  95. String::from("level_since"),
  96. String::from("could not be converted to u32"),
  97. ))
  98. }
  99. };
  100. // The trust level has to be at least 1
  101. let trust_level: u32 = match scalar_u32(&L.trust_level.unwrap()) {
  102. Some(v) => v,
  103. None => {
  104. return Err(CredentialError::InvalidField(
  105. String::from("trust_level"),
  106. String::from("could not be converted to u32"),
  107. ))
  108. }
  109. };
  110. if trust_level < 1 || (trust_level as usize) > MAX_LEVEL {
  111. return Err(CredentialError::InvalidField(
  112. String::from("trust_level"),
  113. format!("level {:?} not in range", trust_level),
  114. ));
  115. }
  116. // The trust level has to be no higher than the highest level
  117. let level_interval: u32 = match LEVEL_INTERVAL.get(trust_level as usize) {
  118. Some(&v) => v,
  119. None => {
  120. return Err(CredentialError::InvalidField(
  121. String::from("trust_level"),
  122. format!("level {:?} not in range", trust_level),
  123. ))
  124. }
  125. };
  126. if level_since + level_interval > today {
  127. return Err(CredentialError::TimeThresholdNotMet(
  128. level_since + level_interval - today,
  129. ));
  130. }
  131. // The credential can't be _too_ old
  132. let diffdays = today - (level_since + level_interval);
  133. if diffdays > 511 {
  134. return Err(CredentialError::CredentialExpired);
  135. }
  136. // The current number of blockages
  137. let blockages = match scalar_u32(&L.blockages.unwrap()) {
  138. Some(v) => v,
  139. None => {
  140. return Err(CredentialError::InvalidField(
  141. String::from("blockages"),
  142. String::from("could not be converted to u32"),
  143. ))
  144. }
  145. };
  146. if blockages > MAX_BLOCKAGES[trust_level as usize] {
  147. return Err(CredentialError::ExceededBlockagesThreshold);
  148. }
  149. // The buckets in the Lox and Bucket Reachability credentials have
  150. // to match
  151. if L.bucket.is_some_and(|b| b != B.bucket.unwrap()) {
  152. return Err(CredentialError::CredentialMismatch);
  153. }
  154. // The Bucket Reachability credential has to be dated today
  155. let reach_date: u32 = match scalar_u32(&B.date.unwrap()) {
  156. Some(v) => v,
  157. None => {
  158. return Err(CredentialError::InvalidField(
  159. String::from("date"),
  160. String::from("could not be converted to u32"),
  161. ))
  162. }
  163. };
  164. if reach_date != today {
  165. return Err(CredentialError::InvalidField(
  166. String::from("date"),
  167. String::from("reachability credential must be generated today"),
  168. ));
  169. }
  170. // The new trust level
  171. let new_level = if (trust_level as usize) < MAX_LEVEL {
  172. trust_level + 1
  173. } else {
  174. trust_level
  175. };
  176. let mut N = Lox::using_pubkey(&pubkeys);
  177. N.id = Some(Scalar::random(rng));
  178. N.bucket = L.bucket;
  179. N.trust_level = Some(new_level.into());
  180. N.level_since = Some(today.into());
  181. N.invites_remaining = Some(LEVEL_INVITATIONS[trust_level as usize].into());
  182. N.blockages = L.blockages;
  183. let eligibility_max_age = today - (LEVEL_INTERVAL[trust_level as usize]);
  184. let params = level_up::Params {
  185. credential_expiry: (eligibility_max_age - 511).into(),
  186. eligibility_max_age: eligibility_max_age.into(),
  187. max_blockage: MAX_BLOCKAGES[new_level as usize].into(),
  188. today: today.into(),
  189. };
  190. match level_up::prepare(rng, SESSION_ID, &L, &B, N, &params) {
  191. Ok(req_state) => Ok(req_state),
  192. Err(e) => Err(CredentialError::CMZError(e)),
  193. }
  194. }
  195. #[cfg(feature = "bridgeauth")]
  196. impl BridgeAuth {
  197. pub fn handle_level_up(
  198. &mut self,
  199. req: level_up::Request,
  200. ) -> Result<level_up::Reply, CredentialError> {
  201. let mut rng = rand::thread_rng();
  202. let reqbytes = req.as_bytes();
  203. let recvreq = level_up::Request::try_from(&reqbytes[..]).unwrap();
  204. let today = self.today();
  205. match level_up::handle(
  206. &mut rng,
  207. SESSION_ID,
  208. recvreq,
  209. |L: &mut Lox, B: &mut BucketReachability, N: &mut Lox| {
  210. let trust_level: u32 = match scalar_u32(&L.trust_level.unwrap()) {
  211. Some(v) if v as usize >= 1 && v as usize <= MAX_LEVEL => v,
  212. _ => {
  213. // This error should be improved i.e., InvalidAttr and the type
  214. // with a description
  215. return Err(CMZError::RevealAttrMissing(
  216. "trust_level",
  217. "Could not be converted to u32 or value not in range",
  218. ));
  219. }
  220. };
  221. let eligibility_max_age: u32 = today - LEVEL_INTERVAL[trust_level as usize];
  222. L.set_privkey(&self.lox_priv);
  223. B.set_privkey(&self.reachability_priv);
  224. N.set_privkey(&self.lox_priv);
  225. N.trust_level = Some((trust_level + 1).into());
  226. N.level_since = Some(today.into());
  227. N.invites_remaining = Some(LEVEL_INVITATIONS[trust_level as usize].into());
  228. Ok(level_up::Params {
  229. credential_expiry: (eligibility_max_age - 511).into(),
  230. eligibility_max_age: eligibility_max_age.into(),
  231. max_blockage: MAX_BLOCKAGES[(trust_level + 1) as usize].into(),
  232. today: today.into(),
  233. })
  234. },
  235. |L: &Lox, _B: &BucketReachability, _N: &Lox| {
  236. if self.id_filter.filter(&L.id.unwrap()) == SeenType::Seen {
  237. return Err(CMZError::RevealAttrMissing("id", ""));
  238. }
  239. Ok(())
  240. },
  241. ) {
  242. Ok((response, (_L_issuer, _B_isser, _N_issuer))) => Ok(response),
  243. Err(e) => Err(CredentialError::CMZError(e)),
  244. }
  245. }
  246. }
  247. pub fn handle_response(
  248. state: level_up::ClientState,
  249. rep: level_up::Reply,
  250. ) -> Result<Lox, CMZError> {
  251. let replybytes = rep.as_bytes();
  252. let recvreply = level_up::Reply::try_from(&replybytes[..]).unwrap();
  253. match state.finalize(recvreply) {
  254. Ok(cred) => Ok(cred),
  255. Err(_e) => Err(CMZError::Unknown),
  256. }
  257. }
  258. #[cfg(all(test, feature = "bridgeauth"))]
  259. mod tests {
  260. use super::*;
  261. use crate::bridge_table;
  262. use crate::mock_auth::TestHarness;
  263. use crate::proto::{
  264. level_up::{self, LEVEL_INTERVAL},
  265. migration, open_invite,
  266. trust_promotion::{self, UNTRUSTED_INTERVAL},
  267. };
  268. #[test]
  269. fn test_level_up() {
  270. let mut th = TestHarness::new();
  271. let rng = &mut rand::thread_rng();
  272. let open_invitation_request = open_invite::request(rng, th.ba.lox_pub.clone());
  273. assert!(
  274. open_invitation_request.is_ok(),
  275. "Open invitation request should succeed"
  276. );
  277. let (request, client_state) = open_invitation_request.unwrap();
  278. let invite = th.bdb.invite();
  279. let open_invitation_response = th.ba.open_invitation(request, &invite.unwrap());
  280. assert!(
  281. open_invitation_response.is_ok(),
  282. "Open invitation response from server should succeed"
  283. );
  284. let (response, _) = open_invitation_response.unwrap();
  285. let creds = open_invite::handle_response(client_state, response);
  286. println!("{}", th.ba.today());
  287. assert!(creds.is_ok(), "Handle response should succeed");
  288. th.advance_days((UNTRUSTED_INTERVAL + 1).try_into().unwrap());
  289. println!("{}", th.ba.today());
  290. let lox_cred = creds.unwrap();
  291. let trust_promo_request = trust_promotion::request(
  292. rng,
  293. lox_cred.clone(),
  294. th.ba.migrationkey_pub.clone(),
  295. th.ba.today(),
  296. );
  297. assert!(
  298. trust_promo_request.is_ok(),
  299. "Trust Promotion request should succeed"
  300. );
  301. let (tp_request, tp_client_state) = trust_promo_request.unwrap();
  302. let trust_promo_response = th.ba.handle_trust_promotion(tp_request);
  303. assert!(
  304. trust_promo_response.is_ok(),
  305. "Trust promotion response from server should succeed"
  306. );
  307. let (response, enc) = trust_promo_response.unwrap();
  308. let mig_cred = trust_promotion::handle_response(
  309. th.ba.migration_pub.clone(),
  310. tp_client_state,
  311. response,
  312. enc,
  313. );
  314. assert!(mig_cred.is_ok(), "Handle response should succeed");
  315. let migration_request = migration::request(
  316. rng,
  317. lox_cred.clone(),
  318. mig_cred.unwrap(),
  319. th.ba.lox_pub.clone(),
  320. );
  321. assert!(
  322. migration_request.is_ok(),
  323. "Migration request should succeed"
  324. );
  325. let (mig_request, mig_client_state) = migration_request.unwrap();
  326. let migration_response = th.ba.handle_migration(mig_request);
  327. assert!(
  328. migration_response.is_ok(),
  329. "Migration response from server should succeed"
  330. );
  331. let response = migration_response.unwrap();
  332. let mut cred = migration::handle_response(mig_client_state, response);
  333. assert!(cred.is_ok(), "Handle response should succeed");
  334. let lox_cred = cred.unwrap();
  335. let trust_level: u32 = scalar_u32(&lox_cred.clone().trust_level.unwrap()).unwrap();
  336. th.advance_days(LEVEL_INTERVAL[trust_level as usize] + 1);
  337. let (id, key) = bridge_table::from_scalar(lox_cred.bucket.unwrap()).unwrap();
  338. let encbuckets = th.ba.enc_bridge_table().clone();
  339. let reach_pub = th.ba.reachability_pub.clone();
  340. let bucket = bridge_table::BridgeTable::decrypt_bucket(
  341. id,
  342. &key,
  343. encbuckets.get(&id).unwrap(),
  344. &reach_pub,
  345. )
  346. .unwrap();
  347. let reachcred = bucket.1.unwrap();
  348. let level_up_request = level_up::request(
  349. rng,
  350. lox_cred.clone(),
  351. reachcred,
  352. th.ba.lox_pub.clone(),
  353. th.ba.today(),
  354. );
  355. assert!(level_up_request.is_ok(), "Level up request should succeed");
  356. let (level_up_request, level_up_client_state) = level_up_request.unwrap();
  357. let level_up_response = th.ba.handle_level_up(level_up_request);
  358. assert!(
  359. level_up_response.is_ok(),
  360. "Level up response from server should succeed"
  361. );
  362. let response = level_up_response.unwrap();
  363. cred = level_up::handle_response(level_up_client_state, response);
  364. assert!(cred.is_ok(), "Handle response should succeed");
  365. th.verify_lox(&cred.unwrap());
  366. }
  367. }