|
|
@@ -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 ),+ ) => {
|