| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193 |
- #![allow(non_snake_case)]
- use chrono::Utc;
- use cmz::*;
- use curve25519_dalek::ristretto::RistrettoPoint;
- use group::{Group, GroupEncoding};
- use rand::{CryptoRng, RngCore};
- use sha2::Sha512;
- use std::collections::HashSet;
- type G = RistrettoPoint;
- CMZ! { Cred: key }
- CMZ! { PresNum: pres_num }
- muCMZProtocol! { issue_cred,
- ,
- N: Cred { key: J },
- }
- muCMZProtocol! { pres_cred<max_pres, @Epoch_base, @VRF_output>,
- [ C: Cred { key: H },
- P?: PresNum { pres_num: H } ],
- ,
- Epoch_base = (C.key + P.pres_num)*VRF_output,
- (0..max_pres).contains(P.pres_num),
- }
- struct RateLimitClient {
- presnum_pubkey: CMZPubkey<G>,
- cred: Cred,
- }
- impl RateLimitClient {
- pub fn new(rng: &mut (impl CryptoRng + RngCore), cred: &Cred) -> Self {
- let (_, presnum_pubkey) = PresNum::mucmz_gen_keys(rng);
- Self {
- presnum_pubkey,
- cred: cred.clone(),
- }
- }
- pub fn pres(
- &mut self,
- rng: &mut (impl CryptoRng + RngCore),
- epoch: &[u8],
- pres_num: u32,
- ) -> Result<Vec<u8>, CMZError> {
- let mut P = PresNum::using_pubkey(&self.presnum_pubkey);
- P.pres_num = Some(pres_num.into());
- P.fake_MAC(rng);
- let Epoch_base = RistrettoPoint::hash_from_bytes::<Sha512>(epoch);
- let VRF_output = (self.cred.key.unwrap() + P.pres_num.unwrap()).invert() * Epoch_base;
- let params = pres_cred::Params {
- max_pres: 5u32.into(),
- Epoch_base,
- VRF_output,
- };
- let (request, _) = pres_cred::prepare(rng, b"pres_cred", &self.cred, &P, ¶ms)?;
- let mut msg: Vec<u8> = Vec::new();
- msg.extend(VRF_output.to_bytes());
- msg.extend(request.as_bytes());
- Ok(msg)
- }
- }
- struct RateLimitServer {
- privkey: CMZPrivkey<G>,
- presnum_privkey: CMZPrivkey<G>,
- seen_tags: HashSet<[u8; 32]>,
- }
- impl RateLimitServer {
- pub fn new(rng: &mut (impl CryptoRng + RngCore), privkey: &CMZPrivkey<G>) -> Self {
- let (presnum_privkey, _) = PresNum::mucmz_gen_keys(rng);
- Self {
- privkey: privkey.clone(),
- presnum_privkey,
- seen_tags: HashSet::new(),
- }
- }
- pub fn check(
- &mut self,
- rng: &mut (impl CryptoRng + RngCore),
- epoch: &[u8],
- msg: &[u8],
- ) -> Result<(), CMZError> {
- let Epoch_base = RistrettoPoint::hash_from_bytes::<Sha512>(epoch);
- // Separate the message into the VRF output and the request
- let VRF_output = G::from_bytes(&msg[..32].try_into().unwrap()).unwrap();
- let request = pres_cred::Request::try_from(&msg[32..]).unwrap();
- let res = pres_cred::handle(
- rng,
- b"pres_cred",
- request,
- |C: &mut Cred, P: &mut PresNum| {
- let params = pres_cred::Params {
- max_pres: 5u32.into(),
- Epoch_base,
- VRF_output,
- };
- C.set_privkey(&self.privkey);
- P.set_privkey(&self.presnum_privkey);
- Ok(params)
- },
- |_C: &Cred, _P: &PresNum| {
- if !self.seen_tags.insert(VRF_output.to_bytes()) {
- print!("(duplicate tag seen) ");
- Err(CMZError::CliProofFailed)
- } else {
- Ok(())
- }
- },
- );
- match res {
- Ok(_) => Ok(()),
- Err(e) => Err(e),
- }
- }
- }
- #[test]
- fn test_rate_limiting() -> Result<(), CMZError> {
- let mut rng = rand::thread_rng();
- cmz_group_init(RistrettoPoint::hash_from_bytes::<Sha512>(
- b"CMZ Generator A",
- ));
- let (privkey, pubkey) = Cred::mucmz_gen_keys(&mut rng);
- // Issue the credential
- let (request, state) =
- issue_cred::prepare(&mut rng, b"issue_cred", Cred::using_pubkey(&pubkey))?;
- let (reply, _) = issue_cred::handle(
- &mut rng,
- b"issue_cred",
- request,
- |C: &mut Cred| {
- C.set_privkey(&privkey);
- Ok(())
- },
- |_C: &Cred| Ok(()),
- )?;
- let res = state.finalize(reply);
- let cred = match res {
- Ok(c) => c,
- Err((err, _state)) => Err(err)?,
- };
- let mut client = RateLimitClient::new(&mut rng, &cred);
- let mut server = RateLimitServer::new(&mut rng, &privkey);
- let today = Utc::now().date_naive().format("Epoch %Y-%m-%d").to_string();
- let mut run_test = |pres_num: u32, should_succeed: bool| {
- print!("Presenting {pres_num}: ");
- let msg = client.pres(&mut rng, today.as_bytes(), pres_num).unwrap();
- let res = server.check(&mut rng, today.as_bytes(), &msg);
- match res {
- Ok(_) => {
- if should_succeed {
- println!("success");
- } else {
- println!("succeeded but should have failed!");
- res.unwrap_err();
- }
- }
- Err(_) => {
- if should_succeed {
- println!("fail!");
- res.unwrap();
- } else {
- println!("failed as expected");
- }
- }
- }
- };
- run_test(3, true);
- run_test(4, true);
- run_test(2, true);
- // Should fail, because we've presented #3 already
- run_test(3, false);
- // Should fail, because 5 is out of range of (0..5) = {0,1,2,3,4}
- run_test(5, false);
- run_test(0, true);
- run_test(1, true);
- Ok(())
- }
|