3
2

rate_limiting.rs 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. #![allow(non_snake_case)]
  2. use chrono::Utc;
  3. use cmz::*;
  4. use curve25519_dalek::ristretto::RistrettoPoint;
  5. use group::{Group, GroupEncoding};
  6. use hash2group::{rfc9380::ExpandMsgXmd, FromHash};
  7. use rand::{CryptoRng, RngCore};
  8. use sha2::Sha512;
  9. use std::collections::HashSet;
  10. type G = RistrettoPoint;
  11. const EPOCH_BASE_DST: &[u8] = b"CMZ_RATE_LIMIT_EPOCH_XMD:SHA-512_RO_V1_";
  12. fn epoch_base(epoch: &[u8]) -> G {
  13. <G as FromHash<ExpandMsgXmd<Sha512>>>::from_hash(EPOCH_BASE_DST, epoch)
  14. }
  15. CMZ! { Cred: key }
  16. CMZ! { PresNum: pres_num }
  17. muCMZProtocol! { issue_cred,
  18. ,
  19. N: Cred { key: J },
  20. }
  21. muCMZProtocol! { pres_cred<max_pres, @Epoch_base, @VRF_output>,
  22. [ C: Cred { key: H },
  23. P?: PresNum { pres_num: H } ],
  24. ,
  25. Epoch_base = (C.key + P.pres_num)*VRF_output,
  26. (0..max_pres).contains(P.pres_num),
  27. }
  28. struct RateLimitClient {
  29. presnum_pubkey: CMZPubkey<G>,
  30. cred: Cred,
  31. }
  32. impl RateLimitClient {
  33. pub fn new(rng: &mut (impl CryptoRng + RngCore), cred: &Cred) -> Self {
  34. let (_, presnum_pubkey) = PresNum::mucmz_gen_keys(rng);
  35. Self {
  36. presnum_pubkey,
  37. cred: cred.clone(),
  38. }
  39. }
  40. pub fn pres(
  41. &mut self,
  42. rng: &mut (impl CryptoRng + RngCore),
  43. epoch: &[u8],
  44. pres_num: u32,
  45. ) -> Result<Vec<u8>, CMZError> {
  46. let mut P = PresNum::using_pubkey(&self.presnum_pubkey);
  47. P.pres_num = Some(pres_num.into());
  48. P.fake_MAC(rng);
  49. let Epoch_base = epoch_base(epoch);
  50. let VRF_output = (self.cred.key.unwrap() + P.pres_num.unwrap()).invert() * Epoch_base;
  51. let params = pres_cred::Params {
  52. max_pres: 5u32.into(),
  53. Epoch_base,
  54. VRF_output,
  55. };
  56. let (request, _) = pres_cred::prepare(rng, b"pres_cred", &self.cred, &P, &params)?;
  57. let mut msg: Vec<u8> = Vec::new();
  58. msg.extend(VRF_output.to_bytes());
  59. msg.extend(request.as_bytes());
  60. Ok(msg)
  61. }
  62. }
  63. struct RateLimitServer {
  64. privkey: CMZPrivkey<G>,
  65. presnum_privkey: CMZPrivkey<G>,
  66. seen_tags: HashSet<[u8; 32]>,
  67. }
  68. impl RateLimitServer {
  69. pub fn new(rng: &mut (impl CryptoRng + RngCore), privkey: &CMZPrivkey<G>) -> Self {
  70. let (presnum_privkey, _) = PresNum::mucmz_gen_keys(rng);
  71. Self {
  72. privkey: privkey.clone(),
  73. presnum_privkey,
  74. seen_tags: HashSet::new(),
  75. }
  76. }
  77. pub fn check(
  78. &mut self,
  79. rng: &mut (impl CryptoRng + RngCore),
  80. epoch: &[u8],
  81. msg: &[u8],
  82. ) -> Result<(), CMZError> {
  83. let Epoch_base = epoch_base(epoch);
  84. // Separate the message into the VRF output and the request
  85. let VRF_output = G::from_bytes(&msg[..32].try_into().unwrap()).unwrap();
  86. let request = pres_cred::Request::try_from(&msg[32..]).unwrap();
  87. let res = pres_cred::handle(
  88. rng,
  89. b"pres_cred",
  90. request,
  91. |C: &mut Cred, P: &mut PresNum| {
  92. let params = pres_cred::Params {
  93. max_pres: 5u32.into(),
  94. Epoch_base,
  95. VRF_output,
  96. };
  97. C.set_privkey(&self.privkey);
  98. P.set_privkey(&self.presnum_privkey);
  99. Ok(params)
  100. },
  101. |_C: &Cred, _P: &PresNum| {
  102. if !self.seen_tags.insert(VRF_output.to_bytes()) {
  103. print!("(duplicate tag seen) ");
  104. Err(CMZError::CliProofFailed)
  105. } else {
  106. Ok(())
  107. }
  108. },
  109. );
  110. match res {
  111. Ok(_) => Ok(()),
  112. Err(e) => Err(e),
  113. }
  114. }
  115. }
  116. #[test]
  117. fn test_rate_limiting() -> Result<(), CMZError> {
  118. let mut rng = rand::thread_rng();
  119. let (privkey, pubkey) = Cred::mucmz_gen_keys(&mut rng);
  120. // Issue the credential
  121. let (request, state) =
  122. issue_cred::prepare(&mut rng, b"issue_cred", Cred::using_pubkey(&pubkey))?;
  123. let (reply, _) = issue_cred::handle(
  124. &mut rng,
  125. b"issue_cred",
  126. request,
  127. |C: &mut Cred| {
  128. C.set_privkey(&privkey);
  129. Ok(())
  130. },
  131. |_C: &Cred| Ok(()),
  132. )?;
  133. let res = state.finalize(reply);
  134. let cred = match res {
  135. Ok(c) => c,
  136. Err((err, _state)) => Err(err)?,
  137. };
  138. let mut client = RateLimitClient::new(&mut rng, &cred);
  139. let mut server = RateLimitServer::new(&mut rng, &privkey);
  140. let today = Utc::now().date_naive().format("Epoch %Y-%m-%d").to_string();
  141. let mut run_test = |pres_num: u32, should_succeed: bool| {
  142. print!("Presenting {pres_num}: ");
  143. let msg = client.pres(&mut rng, today.as_bytes(), pres_num).unwrap();
  144. let res = server.check(&mut rng, today.as_bytes(), &msg);
  145. match res {
  146. Ok(_) => {
  147. if should_succeed {
  148. println!("success");
  149. } else {
  150. println!("succeeded but should have failed!");
  151. res.unwrap_err();
  152. }
  153. }
  154. Err(_) => {
  155. if should_succeed {
  156. println!("fail!");
  157. res.unwrap();
  158. } else {
  159. println!("failed as expected");
  160. }
  161. }
  162. }
  163. };
  164. run_test(3, true);
  165. run_test(4, true);
  166. run_test(2, true);
  167. // Should fail, because we've presented #3 already
  168. run_test(3, false);
  169. // Should fail, because 5 is out of range of (0..5) = {0,1,2,3,4}
  170. run_test(5, false);
  171. run_test(0, true);
  172. run_test(1, true);
  173. Ok(())
  174. }
  175. #[test]
  176. fn epoch_base_derivation_is_stable() {
  177. assert_eq!(
  178. hex::encode(epoch_base(b"Epoch 2026-08-16").to_bytes()),
  179. "cabdff610ae6898b12cc86be131d101913b807180f8ffe741319a065d3c2d576"
  180. );
  181. }