Browse Source

feat!: derive CMZ generator A automatically

Derive and memoize the auxiliary generator with RFC 9380 hash-to-group, while retaining explicit initialization as a pre-first-use compatibility override.

BREAKING CHANGE: CMZ credential point types must implement the encoding and SHA-512 hash-to-group bounds captured by CMZGroup.
Michele Orrù 2 weeks ago
parent
commit
c3a5b0fe18
11 changed files with 174 additions and 73 deletions
  1. 16 0
      CHANGELOG.md
  2. 44 4
      Cargo.lock
  3. 15 1
      Cargo.toml
  4. 17 3
      README.md
  5. 59 34
      src/lib.rs
  6. 0 4
      tests/basic.rs
  7. 0 7
      tests/priv_serialize.rs
  8. 1 4
      tests/rate_limiting.rs
  9. 0 4
      tests/submodule.rs
  10. 22 9
      tests/two_groups.rs
  11. 0 3
      tests/wallet.rs

+ 16 - 0
CHANGELOG.md

@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [Unreleased]
+
+### Added
+
+- Derive the CMZ generator `A` automatically via a RFC 9380 hash-to-group of the generator.
+  Applications no longer need to call `cmz_group_init` at startup.
+- Add `CMZGroup`, `cmz_generator_a`, and curve features for the automatic
+  derivation. Ristretto support is enabled by default; P-256 and secp256k1 are
+  available through the `p256` and `k256` features.
+
+### Changes
+
+- CMZ credential point types must now implement
+  `group::prime::PrimeGroup + group::GroupEncoding`, and
+  `hash2group::FromHash<hash2group::rfc9380::ExpandMsgXmd<sha2::Sha512>>`.
+
 ## [0.3.0] - 2026-08-04
 
 ### Changes

+ 44 - 4
Cargo.lock

@@ -159,6 +159,12 @@ version = "3.20.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
 
+[[package]]
+name = "bytemuck"
+version = "1.25.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
+
 [[package]]
 name = "byteorder"
 version = "1.5.0"
@@ -245,6 +251,7 @@ dependencies = [
  "ff",
  "generic_static",
  "group",
+ "hash2group",
  "hex",
  "lazy_static",
  "p256",
@@ -253,7 +260,8 @@ dependencies = [
  "serde",
  "serde_bytes",
  "serde_with",
- "sha2",
+ "sha2 0.10.9",
+ "sha2 0.11.0",
  "sigma-compiler",
  "thiserror",
 ]
@@ -302,6 +310,12 @@ version = "0.9.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
 
+[[package]]
+name = "const-oid"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
+
 [[package]]
 name = "core-foundation-sys"
 version = "0.8.7"
@@ -498,7 +512,7 @@ version = "0.7.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
 dependencies = [
- "const-oid",
+ "const-oid 0.9.6",
  "pem-rfc7468",
  "zeroize",
 ]
@@ -519,7 +533,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
 dependencies = [
  "block-buffer 0.10.4",
- "const-oid",
+ "const-oid 0.9.6",
  "crypto-common 0.1.6",
  "subtle",
 ]
@@ -531,6 +545,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
 dependencies = [
  "block-buffer 0.12.1",
+ "const-oid 0.10.2",
  "crypto-common 0.2.2",
  "zeroize",
 ]
@@ -711,6 +726,20 @@ dependencies = [
  "subtle",
 ]
 
+[[package]]
+name = "hash2group"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d84bb44683b8eecf4a6a2ea1788588995028b2acc0437439f78d330ffaa3474f"
+dependencies = [
+ "bytemuck",
+ "curve25519-dalek",
+ "digest 0.11.3",
+ "elliptic-curve",
+ "k256",
+ "p256",
+]
+
 [[package]]
 name = "hash32"
 version = "0.2.1"
@@ -1007,7 +1036,7 @@ dependencies = [
  "ecdsa",
  "elliptic-curve",
  "primeorder",
- "sha2",
+ "sha2 0.10.9",
 ]
 
 [[package]]
@@ -1349,6 +1378,17 @@ dependencies = [
  "digest 0.10.7",
 ]
 
+[[package]]
+name = "sha2"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.0",
+ "digest 0.11.3",
+]
+
 [[package]]
 name = "shake"
 version = "0.1.0"

+ 15 - 1
Cargo.toml

@@ -11,6 +11,7 @@ cmz-derive = "=0.3.0"
 ff = "0.13"
 generic_static = "0.2"
 group = "0.13"
+hash2group = { version = "0.1.1", features = [ "rfc9380" ] }
 hex = { version = "0.4", features = [ "serde" ] }
 lazy_static = "1"
 postcard = { version = "1", features = [ "alloc" ] }
@@ -18,6 +19,7 @@ rand = "0.8.5"
 serde = { version = "1", features = [ "derive" ] }
 serde_bytes = "0.11"
 serde_with = "3"
+sha2 = "0.11"
 sigma-compiler = "0.2.3"
 thiserror = "2"
 
@@ -26,7 +28,11 @@ chrono = "0.4"
 curve25519-dalek = { version = "4", features = [ "group", "rand_core", "digest" ] }
 # Used for testing co-habitating with a second PrimeGroup.
 p256 = { version = "0.13", features = [ "arithmetic" ] }
-sha2 = "0.10"
+# Enable both test group implementations regardless of the cmz feature set.
+hash2group = { version = "0.1.1", features = [ "curve25519-dalek", "p256" ] }
+# curve25519-dalek 4's legacy hash_from_bytes API used by the rate-limiting
+# fixture is tied to digest/sha2 0.10.
+sha2_10 = { package = "sha2", version = "0.10" }
 
 [patch.crates-io]
 cmz-derive = { path = "cmz-derive" }
@@ -40,6 +46,14 @@ sigma-proofs = { git = "https://github.com/sigma-rs/sigma-proofs", branch = "mu/
 spongefish = { git = "https://github.com/arkworks-rs/spongefish", branch = "mu/draft-v3" }
 
 [features]
+# Hash-to-group implementations available to the automatic CMZ generator
+# derivation. Ristretto remains the default group used by the examples and
+# downstream applications; other groups are opt-in to avoid pulling every
+# curve implementation into every build.
+default = [ "curve25519-dalek" ]
+curve25519-dalek = [ "hash2group/curve25519-dalek" ]
+k256 = [ "hash2group/k256" ]
+p256 = [ "hash2group/p256" ]
 # Dump (to stdout or to a string) the value of the instance on both the
 # prover's and verifier's side.  They should match.
 dump = [ "sigma-compiler/dump" ]

+ 17 - 3
README.md

@@ -17,10 +17,24 @@ to create the credential.)
 Your application can have multiple different kinds of credentials, each
 with its own set of attributes.  All of the credentials in your
 application should use the _same_ `Scalar` and `Point` types.  You get
-these from a mathematical _group_, which must satisfy the trait
-[group::prime::PrimeGroup](https://docs.rs/group/0.13.0/group/prime/trait.PrimeGroup.html).
-A typical such group would be
+these from a mathematical _group_. The `Point` type must implement all of
+the following bounds:
+
+  - `group::prime::PrimeGroup`
+  - `group::GroupEncoding`
+  - `hash2group::FromHash<hash2group::rfc9380::ExpandMsgXmd<sha2::Sha512>>`
+
+`CMZGroup` is a blanket marker trait for exactly this combination of bounds.
+A typical point type satisfying them is
 [curve25519\_dalek::ristretto::RistrettoPoint](https://docs.rs/curve25519-dalek/4.1.3/curve25519_dalek/ristretto/struct.RistrettoPoint.html).
+The default `curve25519-dalek` feature supplies the hash-to-group
+implementation for `RistrettoPoint`; the optional `p256` and `k256` features
+supply it for `p256::ProjectivePoint` and `k256::ProjectivePoint`, respectively.
+
+CMZ requires a second generator `A`, which is derived automatically on first use
+by hashing the canonical encoding of `B` and caching it. Callers may override it
+by calling `cmz_group_init` or `cmz_group_init_with` before the first CMZ
+operation.
 
 To declare a credential type, use the `CMZ!` macro at the top level of
 your crate or module (outside of any function):

+ 59 - 34
src/lib.rs

@@ -11,11 +11,13 @@ use group::prime::PrimeGroup;
 use group::{Group, GroupEncoding};
 #[cfg(feature = "wnaf_is_constant_time")]
 use group::{WnafBase, WnafScalar};
+use hash2group::{rfc9380::ExpandMsgXmd, FromHash};
 use lazy_static::lazy_static;
 use rand::RngCore;
 use serde::{Deserialize, Deserializer, Serialize, Serializer};
 pub use serde_with::serde_as;
 use serde_with::{DeserializeAs, SerializeAs};
+use sha2::Sha512;
 use sigma_compiler::*;
 pub use sigma_compiler::{self};
 use thiserror::Error;
@@ -111,6 +113,36 @@ pub struct CMZPubkey<G: PrimeGroup> {
     pub X: Vec<G>,
 }
 
+/// Domain separator for deriving the CMZ generator `A`.
+///
+/// This value is a public protocol parameter, it is the computationally-independent
+/// auxiliary group generator.
+/// Changing it changes `A`, and thus it is a breaking change for existing CMZ keys
+/// and credentials.
+pub const CMZ_GENERATOR_A_DST: &[u8] = b"CMZ_GENERATOR_A_XMD:SHA-512_RO_V1_";
+
+/// A prime-order group supporting encoding and hash into the group.
+///
+/// The blanket implementation covers groups supported by [`hash2group`].
+pub trait CMZGroup: PrimeGroup + GroupEncoding + FromHash<ExpandMsgXmd<Sha512>> {}
+
+impl<G> CMZGroup for G where G: PrimeGroup + GroupEncoding + FromHash<ExpandMsgXmd<Sha512>> {}
+
+/// Deterministically derive the CMZ generator `A` from the standard generator
+/// `B` using hash-to-group.
+pub fn cmz_generator_a<G: CMZGroup>() -> G {
+    let generator_b = G::generator();
+    let encoded_b = generator_b.to_bytes();
+    let generator_a =
+        <G as FromHash<ExpandMsgXmd<Sha512>>>::from_hash(CMZ_GENERATOR_A_DST, encoded_b.as_ref());
+
+    assert!(
+        !bool::from(generator_a.is_identity()) && generator_a != generator_b,
+        "hash-to-group produced an invalid CMZ generator A"
+    );
+    generator_a
+}
+
 // The size of the WNAF windows.  Larger sizes take more memory, but
 // result in faster multiplications.
 #[cfg(feature = "wnaf_is_constant_time")]
@@ -255,67 +287,59 @@ fn load_bp_with<G: Group>(init: impl FnOnce() -> CMZBasepoints<G>) -> &'static C
         .unwrap()
 }
 
-/// The already-loaded `CMZBasepoints<G>`; panics if `cmz_group_init` has
-/// not been called for `G`.
-fn load_bp<G: Group>() -> &'static CMZBasepoints<G> {
-    load_bp_with::<G>(|| panic!("basepoints uninitialized"))
+/// Return the basepoints for `G`, deriving and memoizing `A` on first use.
+fn load_bp<G: CMZGroup>() -> &'static CMZBasepoints<G> {
+    load_bp_with::<G>(|| CMZBasepoints::init(cmz_generator_a::<G>()))
 }
 
-/// Initialize the required second generator for a `PrimeGroup`.
+/// Override the automatically derived second generator for a `PrimeGroup`.
 ///
 /// CMZ credentials require two generators, `A` and `B`.  `B` is the
 /// "standard" generator.  A can be any other generator (that is, any
 /// other non-identity point in a prime-order group), but it is required
 /// that no one know the discrete log between `A` and `B`.  So you can't
 /// generate `A` by multiplying `B` by some scalar, for example.  If your
-/// group has a hash_from_bytes function, then you can use that to generate
-/// `A`. For example, if your group is a curve25519 group, you can
+/// group has a hash-to-group function, then you can use that to generate
+/// `A`. For the standard CMZ derivation, use [`cmz_generator_a`]:
 ///
 /// ```
-/// use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT as B;
+/// use cmz::cmz_generator_a;
 /// use curve25519_dalek::ristretto::RistrettoPoint as G;
-/// use sha2::Sha512;
-/// let A = G::hash_from_bytes::<Sha512>(b"CMZ Generator A");
-/// assert_ne!(A, B);
+/// use group::Group;
+/// let A = cmz_generator_a::<G>();
+/// assert_ne!(A, G::generator());
 /// ```
 ///
 /// Otherwise, you're possibly on your own to generate an appropriate
-/// generator `A`.  Everyone who uses a given credential type with a
-/// given group will need to use the same `A`.  You need to call this
-/// before doing any operations with a credential.
+/// generator `A`. Everyone who uses a given credential type with a given
+/// group will need to use the same `A`.
+///
+/// CMZ automatically derives `A` using [`cmz_generator_a`], so normal callers
+/// do not need this function. This override exists for compatibility with
+/// deployments using older or application-specific public parameters. It must
+/// be called before the first CMZ operation for `G`; later calls retain the
+/// already-loaded value.
 pub fn cmz_group_init<G: PrimeGroup>(generator_A: G) {
     load_bp_with(|| CMZBasepoints::<G>::init(generator_A));
 }
 
-/// [`cmz_group_init`], deferring construction of `generator_A` to the call
-/// that actually initializes the group.
+/// [`cmz_group_init`], deferring construction of a custom `generator_A` to the
+/// call that actually initializes the group.
 ///
-/// The basepoints are memoized, so calling `cmz_group_init` once per request
-/// is harmless -- except that its argument is evaluated first, every time.
-/// Callers typically derive `A` by hashing to the curve, which costs far more
-/// than the initialization it feeds:
+/// This is only needed for compatibility with custom public parameters. The
+/// default generator is derived automatically by [`cmz_generator_a`].
 ///
-/// ```no_run
-/// # use cmz::{cmz_group_init, cmz_group_init_with};
-/// # use curve25519_dalek::ristretto::RistrettoPoint as G;
-/// # use sha2::Sha512;
-/// // ~4.7us of hash-to-curve on every call, thrown away after the first:
-/// cmz_group_init(G::hash_from_bytes::<Sha512>(b"CMZ Generator A"));
-///
-/// // hashed once, on the call that initializes:
-/// cmz_group_init_with(|| G::hash_from_bytes::<Sha512>(b"CMZ Generator A"));
-/// ```
 pub fn cmz_group_init_with<G: PrimeGroup>(generator_A: impl FnOnce() -> G) {
     load_bp_with(|| CMZBasepoints::<G>::init(generator_A()));
 }
 
 /// Get the loaded CMZBasepoints for the given group
-pub fn cmz_basepoints<G: PrimeGroup>() -> &'static CMZBasepoints<G> {
+pub fn cmz_basepoints<G: CMZGroup>() -> &'static CMZBasepoints<G> {
     load_bp()
 }
 
 /// Compute a public key from a private key
-pub fn cmz_privkey_to_pubkey<G: PrimeGroup>(privkey: &CMZPrivkey<G>) -> CMZPubkey<G> {
+pub fn cmz_privkey_to_pubkey<G: CMZGroup>(privkey: &CMZPrivkey<G>) -> CMZPubkey<G> {
     let bp = load_bp::<G>();
     let X0: Option<G> = if privkey.muCMZ {
         Some(bp.mulB(&privkey.x0))
@@ -340,7 +364,7 @@ where
     type Scalar: PrimeField;
 
     /// The type of the coordinates of the MAC for this credential
-    type Point: PrimeGroup;
+    type Point: CMZGroup;
 
     /// Produce a vector of strings containing the names of the
     /// attributes of this credential.  (The MAC is not included.)
@@ -490,7 +514,8 @@ where
 ///     use curve25519_dalek::ristretto::RistrettoPoint;
 ///     type G = RistrettoPoint;
 ///
-/// The group must implement the trait [`PrimeGroup`](https://docs.rs/group/latest/group/prime/trait.PrimeGroup.html).
+/// The group must implement [`CMZGroup`]. Implementations are available for
+/// supported hash-to-group curves through the corresponding crate features.
 #[macro_export]
 macro_rules! CMZ {
     ( $name: ident < $G: ident > : $( $id: ident ),+ ) => {

+ 0 - 4
tests/basic.rs

@@ -3,7 +3,6 @@ use curve25519_dalek::ristretto::RistrettoPoint;
 use curve25519_dalek::scalar::Scalar;
 use group::Group;
 use rand::{CryptoRng, RngCore};
-use sha2::Sha512;
 
 CMZ! { Basic<RistrettoPoint> :
     attr1,
@@ -24,9 +23,6 @@ A: Basic {
 #[test]
 fn test_basic() {
     let mut rng = rand::thread_rng();
-    cmz_group_init(RistrettoPoint::hash_from_bytes::<Sha512>(
-        b"CMZ Generator A",
-    ));
 
     let (privkey, pubkey) = Basic::cmz14_gen_keys(&mut rng);
 

+ 0 - 7
tests/priv_serialize.rs

@@ -3,7 +3,6 @@ use curve25519_dalek::ristretto::RistrettoPoint;
 use curve25519_dalek::scalar::Scalar;
 use group::Group;
 use rand::{CryptoRng, RngCore};
-use sha2::Sha512;
 
 CMZ! { Basic<RistrettoPoint> :
     attr1,
@@ -22,9 +21,6 @@ A: Basic {
 #[test]
 fn test_default_serialize() {
     let mut rng = rand::thread_rng();
-    cmz_group_init(RistrettoPoint::hash_from_bytes::<Sha512>(
-        b"CMZ Generator A",
-    ));
 
     let (privkey, pubkey) = Basic::cmz14_gen_keys(&mut rng);
 
@@ -50,9 +46,6 @@ fn test_default_serialize() {
 #[test]
 fn test_priv_serialize() {
     let mut rng = rand::thread_rng();
-    cmz_group_init(RistrettoPoint::hash_from_bytes::<Sha512>(
-        b"CMZ Generator A",
-    ));
 
     let (privkey, pubkey) = Basic::cmz14_gen_keys(&mut rng);
 

+ 1 - 4
tests/rate_limiting.rs

@@ -5,7 +5,7 @@ use cmz::*;
 use curve25519_dalek::ristretto::RistrettoPoint;
 use group::{Group, GroupEncoding};
 use rand::{CryptoRng, RngCore};
-use sha2::Sha512;
+use sha2_10::Sha512;
 use std::collections::HashSet;
 
 type G = RistrettoPoint;
@@ -125,9 +125,6 @@ impl RateLimitServer {
 #[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);
 

+ 0 - 4
tests/submodule.rs

@@ -5,7 +5,6 @@ use curve25519_dalek::ristretto::RistrettoPoint;
 use curve25519_dalek::scalar::Scalar;
 use group::Group;
 use rand::{CryptoRng, RngCore};
-use sha2::Sha512;
 
 pub mod cred {
     use super::*;
@@ -30,9 +29,6 @@ pub mod submod {
     #[test]
     fn test_submodule() -> Result<(), CMZError> {
         let mut rng = rand::thread_rng();
-        cmz_group_init(RistrettoPoint::hash_from_bytes::<Sha512>(
-            b"CMZ Generator A",
-        ));
 
         let (privkey, pubkey) = Basic::cmz14_gen_keys(&mut rng);
 

+ 22 - 9
tests/two_groups.rs

@@ -1,21 +1,34 @@
 //! Basepoint derivation is keyed by group type, so two groups can coexist.
 
-use cmz::{cmz_basepoints, cmz_group_init};
+use cmz::{cmz_basepoints, cmz_generator_a, cmz_group_init};
 use curve25519_dalek::ristretto::RistrettoPoint;
-use group::Group;
-use sha2::Sha512;
+use group::{Group, GroupEncoding};
+
+const RISTRETTO_A_ENCODING: &[u8] = &[
+    0x8e, 0x65, 0xa3, 0x1d, 0xc2, 0x60, 0x08, 0xdf, 0xbc, 0xd9, 0xf6, 0xef, 0xbd, 0x57, 0xf7, 0xcb,
+    0xb2, 0xb3, 0x98, 0x72, 0xf6, 0xfd, 0x32, 0x73, 0x6a, 0x00, 0x7a, 0x08, 0xc2, 0x1e, 0x47, 0x18,
+];
+const P256_A_ENCODING: &[u8] = &[
+    0x02, 0x73, 0xe5, 0x71, 0x95, 0x24, 0x34, 0xf0, 0xd5, 0xac, 0xaa, 0xe0, 0xfc, 0xe7, 0x35, 0x7c,
+    0x45, 0xb2, 0x73, 0xbd, 0x8e, 0x30, 0x4e, 0xe1, 0xac, 0xd9, 0xf2, 0x43, 0xf3, 0x0b, 0x96, 0x18,
+    0x9e,
+];
 
 #[test]
 fn two_groups_coexist() {
-    let ristretto_a = RistrettoPoint::hash_from_bytes::<Sha512>(b"CMZ Generator A");
-    let p256_a = p256::ProjectivePoint::generator() * p256::Scalar::from(42u64);
+    let ristretto_a = cmz_generator_a::<RistrettoPoint>();
+    let p256_a = cmz_generator_a::<p256::ProjectivePoint>();
 
-    cmz_group_init::<RistrettoPoint>(ristretto_a);
-    cmz_group_init::<p256::ProjectivePoint>(p256_a);
+    assert_eq!(&ristretto_a.to_bytes()[..], RISTRETTO_A_ENCODING);
+    assert_eq!(&p256_a.to_bytes()[..], P256_A_ENCODING);
 
-    // Each group must read back its own basepoints, not the other's.
+    // Each group is initialized automatically and reads back its own
+    // basepoints, not the other's.
     assert_eq!(cmz_basepoints::<RistrettoPoint>().A(), ristretto_a);
-    assert_eq!(cmz_basepoints::<RistrettoPoint>().B(), RistrettoPoint::generator());
+    assert_eq!(
+        cmz_basepoints::<RistrettoPoint>().B(),
+        RistrettoPoint::generator()
+    );
     assert_eq!(cmz_basepoints::<p256::ProjectivePoint>().A(), p256_a);
     assert_eq!(
         cmz_basepoints::<p256::ProjectivePoint>().B(),

+ 0 - 3
tests/wallet.rs

@@ -6,7 +6,6 @@ use cmz::*;
 use curve25519_dalek::ristretto::RistrettoPoint as G;
 use group::Group;
 use rand::{CryptoRng, RngCore};
-use sha2::Sha512;
 
 CMZ! { Wallet: randid, balance }
 CMZ! { Item: serialno, price }
@@ -107,9 +106,7 @@ macro_rules! protos_def {
                 }
             }
 
-            // Initialization
             let mut rng = rand::thread_rng();
-            cmz_group_init(G::hash_from_bytes::<Sha512>(b"CMZ Generator A"));
 
             // Issuer: generate private and public keys for each type of
             // credential.  (The client gets a copy of the public keys.)