lib.rs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. // We really want points to be capital letters and scalars to be
  2. // lowercase letters
  3. #![allow(non_snake_case)]
  4. #![doc = include_str!("../README.md")]
  5. pub use cmz_derive::*;
  6. use core::any::Any;
  7. use ff::{Field, PrimeField};
  8. use generic_static::StaticTypeMap;
  9. use group::prime::PrimeGroup;
  10. use group::{Group, GroupEncoding};
  11. #[cfg(feature = "wnaf_is_constant_time")]
  12. use group::{WnafBase, WnafScalar};
  13. use lazy_static::lazy_static;
  14. use rand::Rng;
  15. use serde::{Deserialize, Deserializer, Serialize, Serializer};
  16. pub use serde_with::serde_as;
  17. use serde_with::{DeserializeAs, SerializeAs};
  18. use sigma_compiler::*;
  19. pub use sigma_compiler::{self};
  20. use thiserror::Error;
  21. // We need wrappers for group::Group and ff::PrimeField elements to be
  22. // handled by serde
  23. //
  24. // Pattern from https://docs.rs/serde_with/3.12.0/serde_with/guide/serde_as/index.html
  25. mod group_serde;
  26. /// A wrapper for serializing and deserializing a `Scalar`
  27. pub struct SerdeScalar;
  28. impl<F: PrimeField> SerializeAs<F> for SerdeScalar {
  29. fn serialize_as<S>(value: &F, serializer: S) -> Result<S::Ok, S::Error>
  30. where
  31. S: Serializer,
  32. {
  33. group_serde::serialize_scalar(value, serializer)
  34. }
  35. }
  36. impl<'de, F: PrimeField> DeserializeAs<'de, F> for SerdeScalar {
  37. fn deserialize_as<D>(deserializer: D) -> Result<F, D::Error>
  38. where
  39. D: Deserializer<'de>,
  40. {
  41. group_serde::deserialize_scalar(deserializer)
  42. }
  43. }
  44. /// A wrapper for serializing and deserializing a `Point` (a group
  45. /// element)
  46. pub struct SerdePoint;
  47. impl<G: Group + GroupEncoding> SerializeAs<G> for SerdePoint {
  48. fn serialize_as<S>(value: &G, serializer: S) -> Result<S::Ok, S::Error>
  49. where
  50. S: Serializer,
  51. {
  52. group_serde::serialize_point(value, serializer)
  53. }
  54. }
  55. impl<'de, G: Group + GroupEncoding> DeserializeAs<'de, G> for SerdePoint {
  56. fn deserialize_as<D>(deserializer: D) -> Result<G, D::Error>
  57. where
  58. D: Deserializer<'de>,
  59. {
  60. group_serde::deserialize_point(deserializer)
  61. }
  62. }
  63. /// The CMZMac struct represents a MAC on a CMZ credential.
  64. #[serde_as]
  65. #[derive(Copy, Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
  66. pub struct CMZMac<G: PrimeGroup> {
  67. #[serde_as(as = "SerdePoint")]
  68. pub P: G,
  69. #[serde_as(as = "SerdePoint")]
  70. pub Q: G,
  71. }
  72. /// The CMZPrivkey struct represents a CMZ private key
  73. #[serde_as]
  74. #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
  75. pub struct CMZPrivkey<G: PrimeGroup> {
  76. // Is this key for µCMZ or classic CMZ14?
  77. pub muCMZ: bool,
  78. #[serde_as(as = "SerdeScalar")]
  79. pub x0: <G as Group>::Scalar,
  80. // The next field is xr for µCMZ, and serves the role of x0tilde for
  81. // CMZ14
  82. #[serde_as(as = "SerdeScalar")]
  83. pub xr: <G as Group>::Scalar,
  84. // The elements of x correspond to the attributes of the credential
  85. #[serde_as(as = "Vec<SerdeScalar>")]
  86. pub x: Vec<<G as Group>::Scalar>,
  87. }
  88. /// The CMZPubkey struct represents a CMZ public key
  89. #[serde_as]
  90. #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
  91. pub struct CMZPubkey<G: PrimeGroup> {
  92. #[serde_as(as = "Option<SerdePoint>")]
  93. pub X0: Option<G>,
  94. // Xr is only used for µCMZ, not CMZ14 (where it will be None)
  95. #[serde_as(as = "Option<SerdePoint>")]
  96. pub Xr: Option<G>,
  97. // The elements of X correspond to the attributes of the credential
  98. #[serde_as(as = "Vec<SerdePoint>")]
  99. pub X: Vec<G>,
  100. }
  101. // The size of the WNAF windows. Larger sizes take more memory, but
  102. // result in faster multiplications.
  103. #[cfg(feature = "wnaf_is_constant_time")]
  104. const WNAF_SIZE: usize = 6;
  105. /// A struct (generic over G) holding the two CMZ bases, and their Wnaf
  106. /// basepoint tables
  107. #[derive(Clone)]
  108. pub struct CMZBasepoints<G: Group> {
  109. A_: G,
  110. B_: G,
  111. #[cfg(feature = "wnaf_is_constant_time")]
  112. A_TABLE: WnafBase<G, WNAF_SIZE>,
  113. #[cfg(feature = "wnaf_is_constant_time")]
  114. B_TABLE: WnafBase<G, WNAF_SIZE>,
  115. }
  116. impl<G: Group> CMZBasepoints<G> {
  117. pub fn init(generator_A: G) -> Self {
  118. let A_ = generator_A;
  119. let B_ = G::generator();
  120. #[cfg(feature = "wnaf_is_constant_time")]
  121. let A_TABLE = WnafBase::new(A_);
  122. #[cfg(feature = "wnaf_is_constant_time")]
  123. let B_TABLE = WnafBase::new(B_);
  124. CMZBasepoints {
  125. A_,
  126. B_,
  127. #[cfg(feature = "wnaf_is_constant_time")]
  128. A_TABLE,
  129. #[cfg(feature = "wnaf_is_constant_time")]
  130. B_TABLE,
  131. }
  132. }
  133. #[cfg(feature = "wnaf_is_constant_time")]
  134. pub fn mulA(&self, s: &G::Scalar) -> G {
  135. let wnaf_s = WnafScalar::<G::Scalar, WNAF_SIZE>::new(s);
  136. &self.A_TABLE * &wnaf_s
  137. }
  138. #[cfg(feature = "wnaf_is_constant_time")]
  139. pub fn mulB(&self, s: &G::Scalar) -> G {
  140. let wnaf_s = WnafScalar::<G::Scalar, WNAF_SIZE>::new(s);
  141. &self.B_TABLE * &wnaf_s
  142. }
  143. #[cfg(not(feature = "wnaf_is_constant_time"))]
  144. pub fn mulA(&self, s: &G::Scalar) -> G {
  145. self.A_ * s
  146. }
  147. #[cfg(not(feature = "wnaf_is_constant_time"))]
  148. pub fn mulB(&self, s: &G::Scalar) -> G {
  149. self.B_ * s
  150. }
  151. pub fn keypairA(&self, rng: &mut impl Rng) -> (G::Scalar, G) {
  152. let x = G::Scalar::random(&mut *rng);
  153. (x, self.mulA(&x))
  154. }
  155. pub fn keypairB(&self, rng: &mut impl Rng) -> (G::Scalar, G) {
  156. let x = G::Scalar::random(&mut *rng);
  157. (x, self.mulB(&x))
  158. }
  159. pub fn A(&self) -> G {
  160. self.A_
  161. }
  162. pub fn B(&self) -> G {
  163. self.B_
  164. }
  165. }
  166. // What's going on here needs some explanation. For each group G, we
  167. // want to pre-compute the WnafBase tables in a [`CMZBasepoints`] struct,
  168. // and we want that pre-computed struct to remain globally accessible.
  169. // So ideally, we'd just have a generic static CMZBasepoints<G> struct,
  170. // and instantiate it once for each G that we use.
  171. //
  172. // The tricky bit is that we don't know what group(s) G the programmer
  173. // (the person using this cmz crate) will end up using, and Rust doesn't
  174. // support generic statics.
  175. //
  176. // So what we'd like is a non-generic static _map_ that maps a group
  177. // type G to the precomputed CMZBasepoints<G> struct. But types aren't
  178. // values that can be mapped by a normal HashMap. Luckily, there's a
  179. // generic_static crate that provides a StaticTypeMap that has the
  180. // ability to map types to objects.
  181. //
  182. // However, all of those *mapped-to* objects have to all be of the same
  183. // type, whereas we want the type G to map to a struct of type
  184. // CMZBasepoints<G>, which is different for each value of G.
  185. //
  186. // So we make a non-generic trait CMZbp that all instantiations of
  187. // CMZBasepoints<G> implement (for all group types G), and have the
  188. // StaticTypeMap map each type G to a trait object Box<dyn CMZbp>.
  189. //
  190. // Then to read the CMZBasepoints<G> back out, we look up the trait
  191. // object in the StaticTypeMap, yielding a Box<dyn CMZbp>. We now need
  192. // to downcast this trait object to the concrete type CMZBasepoints<G>,
  193. // for a _specific_ G. Rust provides downcasting, but only from &dyn Any
  194. // to the original concrete type, not from other things like &dyn CMZbp.
  195. // So first we need to upcast the trait object to &dyn Any, which we do
  196. // with an "as_any()" function in the CMZbp trait, and then downcast the
  197. // result to a CMZBasepoints<G> struct.
  198. //
  199. // The up/down casting pattern is from
  200. // https://stackoverflow.com/questions/33687447/how-to-get-a-reference-to-a-concrete-type-from-a-trait-object
  201. // Static objects have to be Sync + Send, so enforce that as part of the
  202. // CMXBP trait
  203. trait CMZbp: Sync + Send {
  204. fn as_any(&self) -> &dyn Any;
  205. }
  206. impl<G: Group> CMZbp for CMZBasepoints<G> {
  207. fn as_any(&self) -> &dyn Any {
  208. self
  209. }
  210. }
  211. // The StaticTypeMap mapping group types G to trait objects Box<dyn CMZbp>
  212. lazy_static! {
  213. static ref basepoints_map: StaticTypeMap<Box<dyn CMZbp>> = StaticTypeMap::new();
  214. }
  215. /// For a given group type `G`, if `bp` is `Some(b)`, then load the
  216. /// mapping from `G` to `b` into the `basepoints_map`. (If a mapping
  217. /// from `G` already exists, the old one will be kept and the new one
  218. /// ignored.) Whether `bp` is `Some(b)` or `None`, this function
  219. /// returns the (possibly new) target of the `basepoints_map`, as a
  220. /// `&'static CMZBasepoints<G>`.
  221. fn load_bp<G: Group>(bp: Option<CMZBasepoints<G>>) -> &'static CMZBasepoints<G> {
  222. match bp {
  223. Some(b) => basepoints_map.call_once::<Box<dyn CMZbp>, _>(|| Box::new(b.clone())),
  224. None => {
  225. basepoints_map.call_once::<Box<dyn CMZbp>, _>(|| panic!("basepoints uninitialized"))
  226. }
  227. }
  228. .as_any()
  229. .downcast_ref::<CMZBasepoints<G>>()
  230. .unwrap()
  231. }
  232. /// Initialize the required second generator for a `PrimeGroup`.
  233. ///
  234. /// CMZ credentials require two generators, `A` and `B`. `B` is the
  235. /// "standard" generator. A can be any other generator (that is, any
  236. /// other non-identity point in a prime-order group), but it is required
  237. /// that no one know the discrete log between `A` and `B`. So you can't
  238. /// generate `A` by multiplying `B` by some scalar, for example. If your
  239. /// group has a hash_from_bytes function, then you can use that to generate
  240. /// `A`. For example, if your group is a curve25519 group, you can
  241. ///
  242. /// ```
  243. /// use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT as B;
  244. /// use curve25519_dalek::ristretto::RistrettoPoint as G;
  245. /// use sha2::Sha512;
  246. /// let A = G::hash_from_bytes::<Sha512>(b"CMZ Generator A");
  247. /// assert_ne!(A, B);
  248. /// ```
  249. ///
  250. /// Otherwise, you're possibly on your own to generate an appropriate
  251. /// generator `A`. Everyone who uses a given credential type with a
  252. /// given group will need to use the same `A`. You need to call this
  253. /// before doing any operations with a credential.
  254. pub fn cmz_group_init<G: PrimeGroup>(generator_A: G) {
  255. let bp = CMZBasepoints::<G>::init(generator_A);
  256. load_bp(Some(bp));
  257. }
  258. /// Get the loaded CMZBasepoints for the given group
  259. pub fn cmz_basepoints<G: PrimeGroup>() -> &'static CMZBasepoints<G> {
  260. load_bp(None)
  261. }
  262. /// Compute a public key from a private key
  263. pub fn cmz_privkey_to_pubkey<G: PrimeGroup>(privkey: &CMZPrivkey<G>) -> CMZPubkey<G> {
  264. let bp = load_bp::<G>(None);
  265. let X0: Option<G> = if privkey.muCMZ {
  266. Some(bp.mulB(&privkey.x0))
  267. } else {
  268. Some(bp.mulA(&privkey.xr) + bp.mulB(&privkey.x0))
  269. };
  270. let Xr: Option<G> = if privkey.muCMZ {
  271. Some(bp.mulA(&privkey.xr))
  272. } else {
  273. None
  274. };
  275. let X: Vec<G> = privkey.x.iter().map(|x| bp.mulA(x)).collect();
  276. CMZPubkey { X0, Xr, X }
  277. }
  278. /// The CMZCredential trait implemented by all CMZ credential struct types.
  279. pub trait CMZCredential
  280. where
  281. for<'a> Self: Default + Sized + serde::Serialize + serde::Deserialize<'a>,
  282. {
  283. /// The type of attributes for this credential
  284. type Scalar: PrimeField;
  285. /// The type of the coordinates of the MAC for this credential
  286. type Point: PrimeGroup;
  287. /// Produce a vector of strings containing the names of the
  288. /// attributes of this credential. (The MAC is not included.)
  289. fn attrs() -> Vec<&'static str>;
  290. /// The number of attributes in this credential
  291. fn num_attrs() -> usize;
  292. /// The attribute number for a given name as a string
  293. fn attr_num(name: &str) -> usize;
  294. /// Get a reference to one of the attributes, specified by name as a
  295. /// string.
  296. fn attr(&self, name: &str) -> &Option<Self::Scalar>;
  297. /// Get a mutable reference to one of the attributes, specified by
  298. /// name as a string.
  299. fn attr_mut(&mut self, name: &str) -> &mut Option<Self::Scalar>;
  300. /// Set the public key for this credential.
  301. fn set_pubkey(&mut self, pubkey: &CMZPubkey<Self::Point>) -> &mut Self;
  302. /// Get a copy of the public key for this credential. If the public
  303. /// key has not yet been set or computed, a pubkey with X0 == None
  304. /// will be returned.
  305. fn get_pubkey(&self) -> &CMZPubkey<Self::Point>;
  306. /// Set the private key for this credential. The public key will
  307. /// automatically be computed from the private key.
  308. fn set_privkey(&mut self, privkey: &CMZPrivkey<Self::Point>) -> &mut Self;
  309. /// Set the private and public keys for this credential.
  310. fn set_keypair(
  311. &mut self,
  312. privkey: &CMZPrivkey<Self::Point>,
  313. pubkey: &CMZPubkey<Self::Point>,
  314. ) -> &mut Self;
  315. /// Get a copy of the private key for this credential. If the
  316. /// private key has not yet been set, a privkey with an empty x
  317. /// vector will be returned.
  318. fn get_privkey(&self) -> &CMZPrivkey<Self::Point>;
  319. /// Get the element of the privkey x vector associated with the
  320. /// given field name
  321. fn privkey_x(&self, name: &str) -> Self::Scalar;
  322. /// Get the element of the pubkey X vector associated with the given
  323. /// field name
  324. fn pubkey_X(&self, name: &str) -> Self::Point;
  325. /// Generate random private and public keys for this credential
  326. /// type. muCMZ should be true if this credential will be issued
  327. /// with muCMZ protocols (and _not_ classic CMZ14 protocols).
  328. fn gen_keys(
  329. rng: &mut impl Rng,
  330. muCMZ: bool,
  331. ) -> (CMZPrivkey<Self::Point>, CMZPubkey<Self::Point>);
  332. /// Convenience functions for the above
  333. fn cmz14_gen_keys(rng: &mut impl Rng) -> (CMZPrivkey<Self::Point>, CMZPubkey<Self::Point>) {
  334. Self::gen_keys(rng, false)
  335. }
  336. fn mucmz_gen_keys(rng: &mut impl Rng) -> (CMZPrivkey<Self::Point>, CMZPubkey<Self::Point>) {
  337. Self::gen_keys(rng, true)
  338. }
  339. /// Convenience function for creating a new Self, and loading the
  340. /// given private key (which will also compute the public key).
  341. fn using_privkey(privkey: &CMZPrivkey<Self::Point>) -> Self {
  342. let mut slf = Self::default();
  343. slf.set_privkey(privkey);
  344. slf
  345. }
  346. /// Convenience function for creating a new Self, and loading the
  347. /// given public key.
  348. fn using_pubkey(pubkey: &CMZPubkey<Self::Point>) -> Self {
  349. let mut slf = Self::default();
  350. slf.set_pubkey(pubkey);
  351. slf
  352. }
  353. /// Create the MAC for this credential, given the private key.
  354. fn create_MAC(
  355. &mut self,
  356. rng: &mut impl Rng,
  357. privkey: &CMZPrivkey<Self::Point>,
  358. ) -> Result<(), ()>;
  359. /// Compute the coefficient component of the MAC (the Scalar you
  360. /// would multiply P by to get Q), given the private key.
  361. fn compute_MAC_coeff(&self, privkey: &CMZPrivkey<Self::Point>) -> Result<Self::Scalar, ()>;
  362. /// Verify the MAC in this credential, given the private key. This
  363. /// is mainly useful for debugging, since the client will not have
  364. /// the private key and the issuer will typically not have the
  365. /// complete credential.
  366. fn verify_MAC(&self, privkey: &CMZPrivkey<Self::Point>) -> Result<(), ()>;
  367. /// Create a fake MAC for this credential. This is useful, for
  368. /// example, when you're doing an OR proof, and in some arms of the
  369. /// disjunction, the credential does not have to be valid.
  370. fn fake_MAC(&mut self, rng: &mut impl Rng);
  371. /// Serialize this credential, _including_ the private key. The
  372. /// default serializer skips the private key for safety reasons.
  373. fn serialize_with_privkey(&self) -> Vec<u8> {
  374. // Accomplish this by serializing the pair (privkey, self)
  375. postcard::to_allocvec(&(self.get_privkey(), self)).unwrap()
  376. }
  377. /// Deserialize this credential, _including_ the private key. The
  378. /// default serializer skips the private key for safety reasons.
  379. fn deserialize_with_privkey(bytes: &[u8]) -> postcard::Result<Self> {
  380. let (privkey, mut cred) = postcard::from_bytes::<(CMZPrivkey<Self::Point>, Self)>(bytes)?;
  381. cred.set_privkey(&privkey);
  382. Ok(cred)
  383. }
  384. }
  385. /// The CMZ macro for declaring CMZ credentials.
  386. ///
  387. /// Use this macro to declare a CMZ credential struct type.
  388. ///
  389. /// use cmz::*;
  390. /// use group::Group;
  391. /// use rand::{CryptoRng, Rng};
  392. /// use curve25519_dalek::ristretto::RistrettoPoint as Grp;
  393. /// CMZ!{ Name<Grp>: attr1, attr2, attr3 }
  394. ///
  395. /// will declare a struct type called `Name`, containing one field for each
  396. /// of the listed attributes. The attribute fields will be of type
  397. /// `Option<Scalar>`. It will also automatically add a field called `MAC`
  398. /// of type [`CMZMac`], and an implementation (via the `CMZCred` derive) of
  399. /// the [`CMZCredential`] trait. The mathematical group used (the field for
  400. /// the values of the attributes and the private key elements, and the group
  401. /// elements for the commitments, MAC components, and public key elements)
  402. /// is `Grp`. If `Grp` is omitted, the macro will default to using a
  403. /// group called `G`, which you can define, for example, as:
  404. ///
  405. /// use curve25519_dalek::ristretto::RistrettoPoint as G;
  406. ///
  407. /// or:
  408. ///
  409. /// use curve25519_dalek::ristretto::RistrettoPoint;
  410. /// type G = RistrettoPoint;
  411. ///
  412. /// The group must implement the trait [`PrimeGroup`](https://docs.rs/group/latest/group/prime/trait.PrimeGroup.html).
  413. #[macro_export]
  414. macro_rules! CMZ {
  415. ( $name: ident < $G: ident > : $( $id: ident ),+ ) => {
  416. #[serde_as]
  417. #[derive(CMZCred,Clone,Debug,Default,serde::Serialize,serde::Deserialize)]
  418. #[cmzcred_group(group = $G)]
  419. pub struct $name {
  420. $(
  421. #[serde_as(as="Option<SerdeScalar>")]
  422. pub $id: Option<<$G as Group>::Scalar>,
  423. )+
  424. pub MAC: CMZMac<$G>,
  425. // Don't serialize the private key by default
  426. #[serde(skip)]
  427. privkey: CMZPrivkey<$G>,
  428. pubkey: CMZPubkey<$G>,
  429. }
  430. };
  431. ( $name: ident : $( $id: ident ),+ ) => {
  432. #[serde_as]
  433. #[derive(CMZCred,Clone,Debug,Default,serde::Serialize,serde::Deserialize)]
  434. #[cmzcred_group(group = G)]
  435. pub struct $name {
  436. $(
  437. #[serde_as(as="Option<SerdeScalar>")]
  438. pub $id: Option<<G as Group>::Scalar>,
  439. )+
  440. pub MAC: CMZMac<G>,
  441. // Don't serialize the private key by default
  442. #[serde(skip)]
  443. privkey: CMZPrivkey<G>,
  444. pubkey: CMZPubkey<G>,
  445. }
  446. };
  447. }
  448. /// The type for errors generated by the prepare, handle, and finalize
  449. /// functions generated by the CMZProtocol family of macros
  450. #[non_exhaustive]
  451. #[derive(Error, Debug)]
  452. pub enum CMZError {
  453. #[error("Hide attribute {1} of credential {0} was not passed to prepare")]
  454. HideAttrMissing(&'static str, &'static str),
  455. #[error("Reveal attribute {1} of credential {0} was not passed to prepare")]
  456. RevealAttrMissing(&'static str, &'static str),
  457. #[error("Implicit attribute {1} of credential {0} was not passed to prepare")]
  458. ImplicitAttrCliMissing(&'static str, &'static str),
  459. #[error("Implicit attribute {1} of credential {0} was not set by fill_creds")]
  460. ImplicitAttrIssMissing(&'static str, &'static str),
  461. #[error("Set attribute {1} of credential {0} was not set by fill_creds")]
  462. SetAttrMissing(&'static str, &'static str),
  463. #[error("private key for credential {0} was not set by fill_creds")]
  464. PrivkeyMissing(&'static str),
  465. #[error("public key for credential {0} was not passed to prepare")]
  466. PubkeyMissing(&'static str),
  467. #[error("credential initialized with wrong protocol")]
  468. WrongProtocol(&'static str),
  469. #[error("client proof did not verify")]
  470. CliProofFailed,
  471. #[error("issuer proof did not verify")]
  472. IssProofFailed,
  473. #[error("unknown CMZ proof error")]
  474. Unknown,
  475. }
  476. #[cfg(test)]
  477. mod tests {
  478. use super::*;
  479. #[test]
  480. fn lox_credential_test() {
  481. use curve25519_dalek::ristretto::RistrettoPoint as G;
  482. CMZ! { Lox:
  483. id,
  484. bucket,
  485. trust_level,
  486. level_since,
  487. invites_remaining,
  488. blockages
  489. }
  490. println!("{:#?}", Lox::attrs());
  491. let mut L = Lox::default();
  492. println!("{:#?}", L);
  493. L.bucket = Some(<G as Group>::Scalar::ONE);
  494. println!("{:#?}", L);
  495. println!("{:#?}", L.attr("bucket"));
  496. *L.attr_mut("id") = Some(<G as Group>::Scalar::ONE);
  497. println!("{:#?}", L);
  498. }
  499. }