//! Named MODP Diffie-Hellman groups.
//!
//! The five RFC 3526 safe-prime groups (14 through 18) used by SSH, IKEv2,
//! legacy TLS, and other finite-field DH consumers, plus a constructor for
//! caller-supplied "custom" groups (RFC 4419 group-exchange).
//!
//! Each named group is exposed as a `fn group14()` / `group15()` / ... that
//! returns an owned [`DhGroup`] — the primes are several hundred bytes each
//! and live in `static` byte arrays, but the [`BoxedUint`] decoding is done
//! lazily at call time so we never pay for groups the program doesn't use.

use super::key::Error;
use crate::bignum::BoxedUint;

/// A named MODP DH group: a safe prime `p` (where `q = (p - 1) / 2` is also
/// prime), a generator `g` of the prime-order subgroup of size `q`, and a
/// recommended private-exponent bit length.
///
/// Constructed via the [`group14`]..[`group18`] free functions for the
/// RFC 3526 groups, or via [`DhGroup::from_custom`] for an RFC 4419
/// group-exchange–supplied `(p, g)` pair.
#[derive(Clone)]
pub struct DhGroup {
    pub(crate) name: &'static str,
    pub(crate) p: BoxedUint,
    pub(crate) g: BoxedUint,
    /// Recommended private-exponent bit length. RFC 7919 §A guides a
    /// `2 × security_bits` value (e.g. 256 bits of exponent for AES-128
    /// equivalent symmetric strength). Used by [`DhPrivateKey::generate`]
    /// to size the random scalar.
    ///
    /// [`DhPrivateKey::generate`]: super::DhPrivateKey::generate
    pub(crate) priv_bits: usize,
}

impl DhGroup {
    /// The group's human-readable name (`"group14"`, `"custom"`, ...).
    pub fn name(&self) -> &'static str {
        self.name
    }

    /// The bit length of `p` — `2048` for group14, `3072` for group15, ...
    pub fn bit_size(&self) -> usize {
        self.p.bit_len()
    }

    /// The byte length of `p`, used for wire-format public-key padding and
    /// shared-secret length.
    pub fn byte_size(&self) -> usize {
        self.p.bit_len().div_ceil(8)
    }

    /// The prime modulus `p`.
    pub fn p(&self) -> &BoxedUint {
        &self.p
    }

    /// The generator `g`.
    pub fn g(&self) -> &BoxedUint {
        &self.g
    }

    /// The recommended private-exponent bit length for this group.
    pub fn priv_bits(&self) -> usize {
        self.priv_bits
    }

    /// Minimum bit length accepted by [`from_custom`](Self::from_custom).
    ///
    /// RFC 4419 §3 states implementations SHOULD NOT accept group-exchange
    /// primes shorter than 1024 bits; modern guidance (RFC 8270 §2, NIST
    /// SP 800-56A Rev 3 §5.5.1.1) raises that floor to 2048. Anything below
    /// 2048 has fewer than ~112 bits of security against the General Number
    /// Field Sieve and falls under the precomputation attacks demonstrated
    /// by the LogJam paper. Callers with a documented legacy interop need
    /// can sidestep this floor by using [`from_custom_unchecked`].
    ///
    /// [`from_custom_unchecked`]: Self::from_custom_unchecked
    pub const MIN_CUSTOM_GROUP_BITS: usize = 2048;

    /// Miller-Rabin rounds used by [`from_custom`](Self::from_custom) on each
    /// of `p` and `(p − 1) / 2`. A composite survives one round with
    /// probability ≤ 1/4 even when adversarially chosen (the bases are not
    /// under attacker control), so 64 rounds bound the false-accept
    /// probability at `4⁻⁶⁴ = 2⁻¹²⁸` — the worst-case guidance for untrusted
    /// candidates (FIPS 186-5 §B.3, RFC 4419 §3 server-supplied groups).
    pub const CUSTOM_GROUP_MR_ROUNDS: usize = 64;

    /// Builds a custom group from a caller-supplied `(p, g)` pair. Used for
    /// RFC 4419 SSH group-exchange where the server transmits the prime and
    /// generator.
    ///
    /// This constructor checks:
    /// * `p` is odd (Montgomery arithmetic requires an odd modulus, and any
    ///   safe prime is odd anyway);
    /// * `p.bit_len() ≥ MIN_CUSTOM_GROUP_BITS` (default 2048) — below that
    ///   threshold the DLP is broken in practice, see RFC 4419 §3 and the
    ///   LogJam precomputation results. Pinned legacy interop can bypass
    ///   via [`from_custom_unchecked`](Self::from_custom_unchecked);
    /// * `g ∈ [2, p - 2]` (the only excluded values are 0, 1, and `p - 1`,
    ///   which are tiny-order elements);
    /// * `p` is a **safe prime**: both `p` and `q = (p − 1) / 2` pass
    ///   [`CUSTOM_GROUP_MR_ROUNDS`] rounds of Miller-Rabin. The runtime
    ///   subgroup-confinement check in `shared_secret` (`y^q == 1`) only
    ///   prevents small-subgroup attacks when `p` is a safe prime — for a
    ///   non-safe `p` with smooth `(p − 1) / 2`, a malicious group leaks the
    ///   private exponent modulo the small factors. The Miller-Rabin bases
    ///   are drawn from an HMAC-DRBG seeded with `p` itself: the verdict is
    ///   deterministic per group, while an adversary cannot precompute a
    ///   composite that fools bases it can't choose independently of `p`.
    ///
    /// It does **not** verify that `g` generates the order-`q` subgroup
    /// (`g` may also generate the full order-`2q` group; `shared_secret`
    /// confines *peer* values to the order-`q` subgroup either way).
    ///
    /// The safe-prime test costs roughly `2 × CUSTOM_GROUP_MR_ROUNDS`
    /// modular exponentiations, so RFC 4419 group-exchange callers should
    /// validate a server-supplied group once and cache the result rather
    /// than re-validating on every handshake.
    ///
    /// [`CUSTOM_GROUP_MR_ROUNDS`]: Self::CUSTOM_GROUP_MR_ROUNDS
    pub fn from_custom(p: BoxedUint, g: BoxedUint, priv_bits: usize) -> Result<Self, Error> {
        use crate::bignum::prime::is_prime_boxed;
        use crate::hash::Sha256;
        use crate::rng::HmacDrbg;

        if p.bit_len() < Self::MIN_CUSTOM_GROUP_BITS {
            return Err(Error::InvalidGroup);
        }
        let group = Self::from_custom_unchecked(p, g, priv_bits)?;

        // Safe-prime validation: p and q = (p - 1) / 2 must both be
        // (probable) primes. Bases come from an HMAC-DRBG seeded with the
        // candidate itself — no ambient RNG is plumbed through this API, and
        // deriving the bases from `p` denies an adversary the fixed bases a
        // precomputed Miller-Rabin pseudoprime would need.
        let mut rng = HmacDrbg::<Sha256>::new(
            &group.p.to_be_bytes(group.byte_size()),
            b"purecrypto-dh-custom-group-mr-bases",
            &[],
        );
        let q = group.p.sub(&BoxedUint::from_u64(1)).shr_bits(1);
        if !is_prime_boxed(&group.p, &mut rng, Self::CUSTOM_GROUP_MR_ROUNDS)
            || !is_prime_boxed(&q, &mut rng, Self::CUSTOM_GROUP_MR_ROUNDS)
        {
            return Err(Error::InvalidGroup);
        }
        Ok(group)
    }

    /// Like [`from_custom`](Self::from_custom) but without the
    /// [`MIN_CUSTOM_GROUP_BITS`](Self::MIN_CUSTOM_GROUP_BITS) floor or the
    /// safe-prime (Miller-Rabin) validation — intended only for tests and
    /// pinned legacy interop where the caller has documented why a
    /// sub-2048-bit or externally validated prime is acceptable. The
    /// structural checks (odd modulus, generator in range, sane
    /// private-exponent bit budget) still apply. Note that the small-subgroup
    /// defense in `shared_secret` assumes a safe prime; the caller owns that
    /// property here.
    pub fn from_custom_unchecked(
        p: BoxedUint,
        g: BoxedUint,
        priv_bits: usize,
    ) -> Result<Self, Error> {
        if !p.is_odd() || p.bit_len() < 3 {
            return Err(Error::InvalidGroup);
        }
        let two = BoxedUint::from_u64(2);
        let p_minus_one = p.sub(&BoxedUint::from_u64(1));
        // g ∈ [2, p - 2]  ⇔  g ≥ 2 AND g < p - 1.
        if g.lt(&two) || !g.lt(&p_minus_one) {
            return Err(Error::InvalidGroup);
        }
        if priv_bits == 0 || priv_bits > p.bit_len() {
            return Err(Error::InvalidGroup);
        }
        Ok(DhGroup {
            name: "custom",
            p,
            g,
            priv_bits,
        })
    }
}

// ---------------------------------------------------------------------------
// RFC 3526 — MODP groups 14, 15, 16, 17, 18.
//
// Primes transcribed verbatim from RFC 3526. `g = 2` for every group. Each
// prime byte length:
//   group14 → 256 bytes (2048 bits)
//   group15 → 384 bytes (3072 bits)
//   group16 → 512 bytes (4096 bits)
//   group17 → 768 bytes (6144 bits)
//   group18 → 1024 bytes (8192 bits)
// ---------------------------------------------------------------------------

/// RFC 3526 §3 — 2048-bit MODP group ("group14"). The SSH
/// `diffie-hellman-group14-sha256` key exchange and the legacy
/// `dh_2048` IKEv2 group.
#[rustfmt::skip]
static GROUP14_P: [u8; 256] = [
    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xC9,0x0F,0xDA,0xA2,0x21,0x68,0xC2,0x34,
    0xC4,0xC6,0x62,0x8B,0x80,0xDC,0x1C,0xD1, 0x29,0x02,0x4E,0x08,0x8A,0x67,0xCC,0x74,
    0x02,0x0B,0xBE,0xA6,0x3B,0x13,0x9B,0x22, 0x51,0x4A,0x08,0x79,0x8E,0x34,0x04,0xDD,
    0xEF,0x95,0x19,0xB3,0xCD,0x3A,0x43,0x1B, 0x30,0x2B,0x0A,0x6D,0xF2,0x5F,0x14,0x37,
    0x4F,0xE1,0x35,0x6D,0x6D,0x51,0xC2,0x45, 0xE4,0x85,0xB5,0x76,0x62,0x5E,0x7E,0xC6,
    0xF4,0x4C,0x42,0xE9,0xA6,0x37,0xED,0x6B, 0x0B,0xFF,0x5C,0xB6,0xF4,0x06,0xB7,0xED,
    0xEE,0x38,0x6B,0xFB,0x5A,0x89,0x9F,0xA5, 0xAE,0x9F,0x24,0x11,0x7C,0x4B,0x1F,0xE6,
    0x49,0x28,0x66,0x51,0xEC,0xE4,0x5B,0x3D, 0xC2,0x00,0x7C,0xB8,0xA1,0x63,0xBF,0x05,
    0x98,0xDA,0x48,0x36,0x1C,0x55,0xD3,0x9A, 0x69,0x16,0x3F,0xA8,0xFD,0x24,0xCF,0x5F,
    0x83,0x65,0x5D,0x23,0xDC,0xA3,0xAD,0x96, 0x1C,0x62,0xF3,0x56,0x20,0x85,0x52,0xBB,
    0x9E,0xD5,0x29,0x07,0x70,0x96,0x96,0x6D, 0x67,0x0C,0x35,0x4E,0x4A,0xBC,0x98,0x04,
    0xF1,0x74,0x6C,0x08,0xCA,0x18,0x21,0x7C, 0x32,0x90,0x5E,0x46,0x2E,0x36,0xCE,0x3B,
    0xE3,0x9E,0x77,0x2C,0x18,0x0E,0x86,0x03, 0x9B,0x27,0x83,0xA2,0xEC,0x07,0xA2,0x8F,
    0xB5,0xC5,0x5D,0xF0,0x6F,0x4C,0x52,0xC9, 0xDE,0x2B,0xCB,0xF6,0x95,0x58,0x17,0x18,
    0x39,0x95,0x49,0x7C,0xEA,0x95,0x6A,0xE5, 0x15,0xD2,0x26,0x18,0x98,0xFA,0x05,0x10,
    0x15,0x72,0x8E,0x5A,0x8A,0xAC,0xAA,0x68, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
];

/// RFC 3526 §4 — 3072-bit MODP group ("group15").
#[rustfmt::skip]
static GROUP15_P: [u8; 384] = [
    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xC9,0x0F,0xDA,0xA2,0x21,0x68,0xC2,0x34,
    0xC4,0xC6,0x62,0x8B,0x80,0xDC,0x1C,0xD1, 0x29,0x02,0x4E,0x08,0x8A,0x67,0xCC,0x74,
    0x02,0x0B,0xBE,0xA6,0x3B,0x13,0x9B,0x22, 0x51,0x4A,0x08,0x79,0x8E,0x34,0x04,0xDD,
    0xEF,0x95,0x19,0xB3,0xCD,0x3A,0x43,0x1B, 0x30,0x2B,0x0A,0x6D,0xF2,0x5F,0x14,0x37,
    0x4F,0xE1,0x35,0x6D,0x6D,0x51,0xC2,0x45, 0xE4,0x85,0xB5,0x76,0x62,0x5E,0x7E,0xC6,
    0xF4,0x4C,0x42,0xE9,0xA6,0x37,0xED,0x6B, 0x0B,0xFF,0x5C,0xB6,0xF4,0x06,0xB7,0xED,
    0xEE,0x38,0x6B,0xFB,0x5A,0x89,0x9F,0xA5, 0xAE,0x9F,0x24,0x11,0x7C,0x4B,0x1F,0xE6,
    0x49,0x28,0x66,0x51,0xEC,0xE4,0x5B,0x3D, 0xC2,0x00,0x7C,0xB8,0xA1,0x63,0xBF,0x05,
    0x98,0xDA,0x48,0x36,0x1C,0x55,0xD3,0x9A, 0x69,0x16,0x3F,0xA8,0xFD,0x24,0xCF,0x5F,
    0x83,0x65,0x5D,0x23,0xDC,0xA3,0xAD,0x96, 0x1C,0x62,0xF3,0x56,0x20,0x85,0x52,0xBB,
    0x9E,0xD5,0x29,0x07,0x70,0x96,0x96,0x6D, 0x67,0x0C,0x35,0x4E,0x4A,0xBC,0x98,0x04,
    0xF1,0x74,0x6C,0x08,0xCA,0x18,0x21,0x7C, 0x32,0x90,0x5E,0x46,0x2E,0x36,0xCE,0x3B,
    0xE3,0x9E,0x77,0x2C,0x18,0x0E,0x86,0x03, 0x9B,0x27,0x83,0xA2,0xEC,0x07,0xA2,0x8F,
    0xB5,0xC5,0x5D,0xF0,0x6F,0x4C,0x52,0xC9, 0xDE,0x2B,0xCB,0xF6,0x95,0x58,0x17,0x18,
    0x39,0x95,0x49,0x7C,0xEA,0x95,0x6A,0xE5, 0x15,0xD2,0x26,0x18,0x98,0xFA,0x05,0x10,
    0x15,0x72,0x8E,0x5A,0x8A,0xAA,0xC4,0x2D, 0xAD,0x33,0x17,0x0D,0x04,0x50,0x7A,0x33,
    0xA8,0x55,0x21,0xAB,0xDF,0x1C,0xBA,0x64, 0xEC,0xFB,0x85,0x04,0x58,0xDB,0xEF,0x0A,
    0x8A,0xEA,0x71,0x57,0x5D,0x06,0x0C,0x7D, 0xB3,0x97,0x0F,0x85,0xA6,0xE1,0xE4,0xC7,
    0xAB,0xF5,0xAE,0x8C,0xDB,0x09,0x33,0xD7, 0x1E,0x8C,0x94,0xE0,0x4A,0x25,0x61,0x9D,
    0xCE,0xE3,0xD2,0x26,0x1A,0xD2,0xEE,0x6B, 0xF1,0x2F,0xFA,0x06,0xD9,0x8A,0x08,0x64,
    0xD8,0x76,0x02,0x73,0x3E,0xC8,0x6A,0x64, 0x52,0x1F,0x2B,0x18,0x17,0x7B,0x20,0x0C,
    0xBB,0xE1,0x17,0x57,0x7A,0x61,0x5D,0x6C, 0x77,0x09,0x88,0xC0,0xBA,0xD9,0x46,0xE2,
    0x08,0xE2,0x4F,0xA0,0x74,0xE5,0xAB,0x31, 0x43,0xDB,0x5B,0xFC,0xE0,0xFD,0x10,0x8E,
    0x4B,0x82,0xD1,0x20,0xA9,0x3A,0xD2,0xCA, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
];

/// RFC 3526 §5 — 4096-bit MODP group ("group16"). The SSH
/// `diffie-hellman-group16-sha512` key exchange.
#[rustfmt::skip]
static GROUP16_P: [u8; 512] = [
    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xC9,0x0F,0xDA,0xA2,0x21,0x68,0xC2,0x34,
    0xC4,0xC6,0x62,0x8B,0x80,0xDC,0x1C,0xD1, 0x29,0x02,0x4E,0x08,0x8A,0x67,0xCC,0x74,
    0x02,0x0B,0xBE,0xA6,0x3B,0x13,0x9B,0x22, 0x51,0x4A,0x08,0x79,0x8E,0x34,0x04,0xDD,
    0xEF,0x95,0x19,0xB3,0xCD,0x3A,0x43,0x1B, 0x30,0x2B,0x0A,0x6D,0xF2,0x5F,0x14,0x37,
    0x4F,0xE1,0x35,0x6D,0x6D,0x51,0xC2,0x45, 0xE4,0x85,0xB5,0x76,0x62,0x5E,0x7E,0xC6,
    0xF4,0x4C,0x42,0xE9,0xA6,0x37,0xED,0x6B, 0x0B,0xFF,0x5C,0xB6,0xF4,0x06,0xB7,0xED,
    0xEE,0x38,0x6B,0xFB,0x5A,0x89,0x9F,0xA5, 0xAE,0x9F,0x24,0x11,0x7C,0x4B,0x1F,0xE6,
    0x49,0x28,0x66,0x51,0xEC,0xE4,0x5B,0x3D, 0xC2,0x00,0x7C,0xB8,0xA1,0x63,0xBF,0x05,
    0x98,0xDA,0x48,0x36,0x1C,0x55,0xD3,0x9A, 0x69,0x16,0x3F,0xA8,0xFD,0x24,0xCF,0x5F,
    0x83,0x65,0x5D,0x23,0xDC,0xA3,0xAD,0x96, 0x1C,0x62,0xF3,0x56,0x20,0x85,0x52,0xBB,
    0x9E,0xD5,0x29,0x07,0x70,0x96,0x96,0x6D, 0x67,0x0C,0x35,0x4E,0x4A,0xBC,0x98,0x04,
    0xF1,0x74,0x6C,0x08,0xCA,0x18,0x21,0x7C, 0x32,0x90,0x5E,0x46,0x2E,0x36,0xCE,0x3B,
    0xE3,0x9E,0x77,0x2C,0x18,0x0E,0x86,0x03, 0x9B,0x27,0x83,0xA2,0xEC,0x07,0xA2,0x8F,
    0xB5,0xC5,0x5D,0xF0,0x6F,0x4C,0x52,0xC9, 0xDE,0x2B,0xCB,0xF6,0x95,0x58,0x17,0x18,
    0x39,0x95,0x49,0x7C,0xEA,0x95,0x6A,0xE5, 0x15,0xD2,0x26,0x18,0x98,0xFA,0x05,0x10,
    0x15,0x72,0x8E,0x5A,0x8A,0xAA,0xC4,0x2D, 0xAD,0x33,0x17,0x0D,0x04,0x50,0x7A,0x33,
    0xA8,0x55,0x21,0xAB,0xDF,0x1C,0xBA,0x64, 0xEC,0xFB,0x85,0x04,0x58,0xDB,0xEF,0x0A,
    0x8A,0xEA,0x71,0x57,0x5D,0x06,0x0C,0x7D, 0xB3,0x97,0x0F,0x85,0xA6,0xE1,0xE4,0xC7,
    0xAB,0xF5,0xAE,0x8C,0xDB,0x09,0x33,0xD7, 0x1E,0x8C,0x94,0xE0,0x4A,0x25,0x61,0x9D,
    0xCE,0xE3,0xD2,0x26,0x1A,0xD2,0xEE,0x6B, 0xF1,0x2F,0xFA,0x06,0xD9,0x8A,0x08,0x64,
    0xD8,0x76,0x02,0x73,0x3E,0xC8,0x6A,0x64, 0x52,0x1F,0x2B,0x18,0x17,0x7B,0x20,0x0C,
    0xBB,0xE1,0x17,0x57,0x7A,0x61,0x5D,0x6C, 0x77,0x09,0x88,0xC0,0xBA,0xD9,0x46,0xE2,
    0x08,0xE2,0x4F,0xA0,0x74,0xE5,0xAB,0x31, 0x43,0xDB,0x5B,0xFC,0xE0,0xFD,0x10,0x8E,
    0x4B,0x82,0xD1,0x20,0xA9,0x21,0x08,0x01, 0x1A,0x72,0x3C,0x12,0xA7,0x87,0xE6,0xD7,
    0x88,0x71,0x9A,0x10,0xBD,0xBA,0x5B,0x26, 0x99,0xC3,0x27,0x18,0x6A,0xF4,0xE2,0x3C,
    0x1A,0x94,0x68,0x34,0xB6,0x15,0x0B,0xDA, 0x25,0x83,0xE9,0xCA,0x2A,0xD4,0x4C,0xE8,
    0xDB,0xBB,0xC2,0xDB,0x04,0xDE,0x8E,0xF9, 0x2E,0x8E,0xFC,0x14,0x1F,0xBE,0xCA,0xA6,
    0x28,0x7C,0x59,0x47,0x4E,0x6B,0xC0,0x5D, 0x99,0xB2,0x96,0x4F,0xA0,0x90,0xC3,0xA2,
    0x23,0x3B,0xA1,0x86,0x51,0x5B,0xE7,0xED, 0x1F,0x61,0x29,0x70,0xCE,0xE2,0xD7,0xAF,
    0xB8,0x1B,0xDD,0x76,0x21,0x70,0x48,0x1C, 0xD0,0x06,0x91,0x27,0xD5,0xB0,0x5A,0xA9,
    0x93,0xB4,0xEA,0x98,0x8D,0x8F,0xDD,0xC1, 0x86,0xFF,0xB7,0xDC,0x90,0xA6,0xC0,0x8F,
    0x4D,0xF4,0x35,0xC9,0x34,0x06,0x31,0x99, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
];

/// RFC 3526 §6 — 6144-bit MODP group ("group17").
#[rustfmt::skip]
static GROUP17_P: [u8; 768] = [
    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xC9,0x0F,0xDA,0xA2,0x21,0x68,0xC2,0x34,
    0xC4,0xC6,0x62,0x8B,0x80,0xDC,0x1C,0xD1, 0x29,0x02,0x4E,0x08,0x8A,0x67,0xCC,0x74,
    0x02,0x0B,0xBE,0xA6,0x3B,0x13,0x9B,0x22, 0x51,0x4A,0x08,0x79,0x8E,0x34,0x04,0xDD,
    0xEF,0x95,0x19,0xB3,0xCD,0x3A,0x43,0x1B, 0x30,0x2B,0x0A,0x6D,0xF2,0x5F,0x14,0x37,
    0x4F,0xE1,0x35,0x6D,0x6D,0x51,0xC2,0x45, 0xE4,0x85,0xB5,0x76,0x62,0x5E,0x7E,0xC6,
    0xF4,0x4C,0x42,0xE9,0xA6,0x37,0xED,0x6B, 0x0B,0xFF,0x5C,0xB6,0xF4,0x06,0xB7,0xED,
    0xEE,0x38,0x6B,0xFB,0x5A,0x89,0x9F,0xA5, 0xAE,0x9F,0x24,0x11,0x7C,0x4B,0x1F,0xE6,
    0x49,0x28,0x66,0x51,0xEC,0xE4,0x5B,0x3D, 0xC2,0x00,0x7C,0xB8,0xA1,0x63,0xBF,0x05,
    0x98,0xDA,0x48,0x36,0x1C,0x55,0xD3,0x9A, 0x69,0x16,0x3F,0xA8,0xFD,0x24,0xCF,0x5F,
    0x83,0x65,0x5D,0x23,0xDC,0xA3,0xAD,0x96, 0x1C,0x62,0xF3,0x56,0x20,0x85,0x52,0xBB,
    0x9E,0xD5,0x29,0x07,0x70,0x96,0x96,0x6D, 0x67,0x0C,0x35,0x4E,0x4A,0xBC,0x98,0x04,
    0xF1,0x74,0x6C,0x08,0xCA,0x18,0x21,0x7C, 0x32,0x90,0x5E,0x46,0x2E,0x36,0xCE,0x3B,
    0xE3,0x9E,0x77,0x2C,0x18,0x0E,0x86,0x03, 0x9B,0x27,0x83,0xA2,0xEC,0x07,0xA2,0x8F,
    0xB5,0xC5,0x5D,0xF0,0x6F,0x4C,0x52,0xC9, 0xDE,0x2B,0xCB,0xF6,0x95,0x58,0x17,0x18,
    0x39,0x95,0x49,0x7C,0xEA,0x95,0x6A,0xE5, 0x15,0xD2,0x26,0x18,0x98,0xFA,0x05,0x10,
    0x15,0x72,0x8E,0x5A,0x8A,0xAA,0xC4,0x2D, 0xAD,0x33,0x17,0x0D,0x04,0x50,0x7A,0x33,
    0xA8,0x55,0x21,0xAB,0xDF,0x1C,0xBA,0x64, 0xEC,0xFB,0x85,0x04,0x58,0xDB,0xEF,0x0A,
    0x8A,0xEA,0x71,0x57,0x5D,0x06,0x0C,0x7D, 0xB3,0x97,0x0F,0x85,0xA6,0xE1,0xE4,0xC7,
    0xAB,0xF5,0xAE,0x8C,0xDB,0x09,0x33,0xD7, 0x1E,0x8C,0x94,0xE0,0x4A,0x25,0x61,0x9D,
    0xCE,0xE3,0xD2,0x26,0x1A,0xD2,0xEE,0x6B, 0xF1,0x2F,0xFA,0x06,0xD9,0x8A,0x08,0x64,
    0xD8,0x76,0x02,0x73,0x3E,0xC8,0x6A,0x64, 0x52,0x1F,0x2B,0x18,0x17,0x7B,0x20,0x0C,
    0xBB,0xE1,0x17,0x57,0x7A,0x61,0x5D,0x6C, 0x77,0x09,0x88,0xC0,0xBA,0xD9,0x46,0xE2,
    0x08,0xE2,0x4F,0xA0,0x74,0xE5,0xAB,0x31, 0x43,0xDB,0x5B,0xFC,0xE0,0xFD,0x10,0x8E,
    0x4B,0x82,0xD1,0x20,0xA9,0x21,0x08,0x01, 0x1A,0x72,0x3C,0x12,0xA7,0x87,0xE6,0xD7,
    0x88,0x71,0x9A,0x10,0xBD,0xBA,0x5B,0x26, 0x99,0xC3,0x27,0x18,0x6A,0xF4,0xE2,0x3C,
    0x1A,0x94,0x68,0x34,0xB6,0x15,0x0B,0xDA, 0x25,0x83,0xE9,0xCA,0x2A,0xD4,0x4C,0xE8,
    0xDB,0xBB,0xC2,0xDB,0x04,0xDE,0x8E,0xF9, 0x2E,0x8E,0xFC,0x14,0x1F,0xBE,0xCA,0xA6,
    0x28,0x7C,0x59,0x47,0x4E,0x6B,0xC0,0x5D, 0x99,0xB2,0x96,0x4F,0xA0,0x90,0xC3,0xA2,
    0x23,0x3B,0xA1,0x86,0x51,0x5B,0xE7,0xED, 0x1F,0x61,0x29,0x70,0xCE,0xE2,0xD7,0xAF,
    0xB8,0x1B,0xDD,0x76,0x21,0x70,0x48,0x1C, 0xD0,0x06,0x91,0x27,0xD5,0xB0,0x5A,0xA9,
    0x93,0xB4,0xEA,0x98,0x8D,0x8F,0xDD,0xC1, 0x86,0xFF,0xB7,0xDC,0x90,0xA6,0xC0,0x8F,
    0x4D,0xF4,0x35,0xC9,0x34,0x02,0x84,0x92, 0x36,0xC3,0xFA,0xB4,0xD2,0x7C,0x70,0x26,
    0xC1,0xD4,0xDC,0xB2,0x60,0x26,0x46,0xDE, 0xC9,0x75,0x1E,0x76,0x3D,0xBA,0x37,0xBD,
    0xF8,0xFF,0x94,0x06,0xAD,0x9E,0x53,0x0E, 0xE5,0xDB,0x38,0x2F,0x41,0x30,0x01,0xAE,
    0xB0,0x6A,0x53,0xED,0x90,0x27,0xD8,0x31, 0x17,0x97,0x27,0xB0,0x86,0x5A,0x89,0x18,
    0xDA,0x3E,0xDB,0xEB,0xCF,0x9B,0x14,0xED, 0x44,0xCE,0x6C,0xBA,0xCE,0xD4,0xBB,0x1B,
    0xDB,0x7F,0x14,0x47,0xE6,0xCC,0x25,0x4B, 0x33,0x20,0x51,0x51,0x2B,0xD7,0xAF,0x42,
    0x6F,0xB8,0xF4,0x01,0x37,0x8C,0xD2,0xBF, 0x59,0x83,0xCA,0x01,0xC6,0x4B,0x92,0xEC,
    0xF0,0x32,0xEA,0x15,0xD1,0x72,0x1D,0x03, 0xF4,0x82,0xD7,0xCE,0x6E,0x74,0xFE,0xF6,
    0xD5,0x5E,0x70,0x2F,0x46,0x98,0x0C,0x82, 0xB5,0xA8,0x40,0x31,0x90,0x0B,0x1C,0x9E,
    0x59,0xE7,0xC9,0x7F,0xBE,0xC7,0xE8,0xF3, 0x23,0xA9,0x7A,0x7E,0x36,0xCC,0x88,0xBE,
    0x0F,0x1D,0x45,0xB7,0xFF,0x58,0x5A,0xC5, 0x4B,0xD4,0x07,0xB2,0x2B,0x41,0x54,0xAA,
    0xCC,0x8F,0x6D,0x7E,0xBF,0x48,0xE1,0xD8, 0x14,0xCC,0x5E,0xD2,0x0F,0x80,0x37,0xE0,
    0xA7,0x97,0x15,0xEE,0xF2,0x9B,0xE3,0x28, 0x06,0xA1,0xD5,0x8B,0xB7,0xC5,0xDA,0x76,
    0xF5,0x50,0xAA,0x3D,0x8A,0x1F,0xBF,0xF0, 0xEB,0x19,0xCC,0xB1,0xA3,0x13,0xD5,0x5C,
    0xDA,0x56,0xC9,0xEC,0x2E,0xF2,0x96,0x32, 0x38,0x7F,0xE8,0xD7,0x6E,0x3C,0x04,0x68,
    0x04,0x3E,0x8F,0x66,0x3F,0x48,0x60,0xEE, 0x12,0xBF,0x2D,0x5B,0x0B,0x74,0x74,0xD6,
    0xE6,0x94,0xF9,0x1E,0x6D,0xCC,0x40,0x24, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
];

/// RFC 3526 §7 — 8192-bit MODP group ("group18").
#[rustfmt::skip]
static GROUP18_P: [u8; 1024] = [
    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xC9,0x0F,0xDA,0xA2,0x21,0x68,0xC2,0x34,
    0xC4,0xC6,0x62,0x8B,0x80,0xDC,0x1C,0xD1, 0x29,0x02,0x4E,0x08,0x8A,0x67,0xCC,0x74,
    0x02,0x0B,0xBE,0xA6,0x3B,0x13,0x9B,0x22, 0x51,0x4A,0x08,0x79,0x8E,0x34,0x04,0xDD,
    0xEF,0x95,0x19,0xB3,0xCD,0x3A,0x43,0x1B, 0x30,0x2B,0x0A,0x6D,0xF2,0x5F,0x14,0x37,
    0x4F,0xE1,0x35,0x6D,0x6D,0x51,0xC2,0x45, 0xE4,0x85,0xB5,0x76,0x62,0x5E,0x7E,0xC6,
    0xF4,0x4C,0x42,0xE9,0xA6,0x37,0xED,0x6B, 0x0B,0xFF,0x5C,0xB6,0xF4,0x06,0xB7,0xED,
    0xEE,0x38,0x6B,0xFB,0x5A,0x89,0x9F,0xA5, 0xAE,0x9F,0x24,0x11,0x7C,0x4B,0x1F,0xE6,
    0x49,0x28,0x66,0x51,0xEC,0xE4,0x5B,0x3D, 0xC2,0x00,0x7C,0xB8,0xA1,0x63,0xBF,0x05,
    0x98,0xDA,0x48,0x36,0x1C,0x55,0xD3,0x9A, 0x69,0x16,0x3F,0xA8,0xFD,0x24,0xCF,0x5F,
    0x83,0x65,0x5D,0x23,0xDC,0xA3,0xAD,0x96, 0x1C,0x62,0xF3,0x56,0x20,0x85,0x52,0xBB,
    0x9E,0xD5,0x29,0x07,0x70,0x96,0x96,0x6D, 0x67,0x0C,0x35,0x4E,0x4A,0xBC,0x98,0x04,
    0xF1,0x74,0x6C,0x08,0xCA,0x18,0x21,0x7C, 0x32,0x90,0x5E,0x46,0x2E,0x36,0xCE,0x3B,
    0xE3,0x9E,0x77,0x2C,0x18,0x0E,0x86,0x03, 0x9B,0x27,0x83,0xA2,0xEC,0x07,0xA2,0x8F,
    0xB5,0xC5,0x5D,0xF0,0x6F,0x4C,0x52,0xC9, 0xDE,0x2B,0xCB,0xF6,0x95,0x58,0x17,0x18,
    0x39,0x95,0x49,0x7C,0xEA,0x95,0x6A,0xE5, 0x15,0xD2,0x26,0x18,0x98,0xFA,0x05,0x10,
    0x15,0x72,0x8E,0x5A,0x8A,0xAA,0xC4,0x2D, 0xAD,0x33,0x17,0x0D,0x04,0x50,0x7A,0x33,
    0xA8,0x55,0x21,0xAB,0xDF,0x1C,0xBA,0x64, 0xEC,0xFB,0x85,0x04,0x58,0xDB,0xEF,0x0A,
    0x8A,0xEA,0x71,0x57,0x5D,0x06,0x0C,0x7D, 0xB3,0x97,0x0F,0x85,0xA6,0xE1,0xE4,0xC7,
    0xAB,0xF5,0xAE,0x8C,0xDB,0x09,0x33,0xD7, 0x1E,0x8C,0x94,0xE0,0x4A,0x25,0x61,0x9D,
    0xCE,0xE3,0xD2,0x26,0x1A,0xD2,0xEE,0x6B, 0xF1,0x2F,0xFA,0x06,0xD9,0x8A,0x08,0x64,
    0xD8,0x76,0x02,0x73,0x3E,0xC8,0x6A,0x64, 0x52,0x1F,0x2B,0x18,0x17,0x7B,0x20,0x0C,
    0xBB,0xE1,0x17,0x57,0x7A,0x61,0x5D,0x6C, 0x77,0x09,0x88,0xC0,0xBA,0xD9,0x46,0xE2,
    0x08,0xE2,0x4F,0xA0,0x74,0xE5,0xAB,0x31, 0x43,0xDB,0x5B,0xFC,0xE0,0xFD,0x10,0x8E,
    0x4B,0x82,0xD1,0x20,0xA9,0x21,0x08,0x01, 0x1A,0x72,0x3C,0x12,0xA7,0x87,0xE6,0xD7,
    0x88,0x71,0x9A,0x10,0xBD,0xBA,0x5B,0x26, 0x99,0xC3,0x27,0x18,0x6A,0xF4,0xE2,0x3C,
    0x1A,0x94,0x68,0x34,0xB6,0x15,0x0B,0xDA, 0x25,0x83,0xE9,0xCA,0x2A,0xD4,0x4C,0xE8,
    0xDB,0xBB,0xC2,0xDB,0x04,0xDE,0x8E,0xF9, 0x2E,0x8E,0xFC,0x14,0x1F,0xBE,0xCA,0xA6,
    0x28,0x7C,0x59,0x47,0x4E,0x6B,0xC0,0x5D, 0x99,0xB2,0x96,0x4F,0xA0,0x90,0xC3,0xA2,
    0x23,0x3B,0xA1,0x86,0x51,0x5B,0xE7,0xED, 0x1F,0x61,0x29,0x70,0xCE,0xE2,0xD7,0xAF,
    0xB8,0x1B,0xDD,0x76,0x21,0x70,0x48,0x1C, 0xD0,0x06,0x91,0x27,0xD5,0xB0,0x5A,0xA9,
    0x93,0xB4,0xEA,0x98,0x8D,0x8F,0xDD,0xC1, 0x86,0xFF,0xB7,0xDC,0x90,0xA6,0xC0,0x8F,
    0x4D,0xF4,0x35,0xC9,0x34,0x02,0x84,0x92, 0x36,0xC3,0xFA,0xB4,0xD2,0x7C,0x70,0x26,
    0xC1,0xD4,0xDC,0xB2,0x60,0x26,0x46,0xDE, 0xC9,0x75,0x1E,0x76,0x3D,0xBA,0x37,0xBD,
    0xF8,0xFF,0x94,0x06,0xAD,0x9E,0x53,0x0E, 0xE5,0xDB,0x38,0x2F,0x41,0x30,0x01,0xAE,
    0xB0,0x6A,0x53,0xED,0x90,0x27,0xD8,0x31, 0x17,0x97,0x27,0xB0,0x86,0x5A,0x89,0x18,
    0xDA,0x3E,0xDB,0xEB,0xCF,0x9B,0x14,0xED, 0x44,0xCE,0x6C,0xBA,0xCE,0xD4,0xBB,0x1B,
    0xDB,0x7F,0x14,0x47,0xE6,0xCC,0x25,0x4B, 0x33,0x20,0x51,0x51,0x2B,0xD7,0xAF,0x42,
    0x6F,0xB8,0xF4,0x01,0x37,0x8C,0xD2,0xBF, 0x59,0x83,0xCA,0x01,0xC6,0x4B,0x92,0xEC,
    0xF0,0x32,0xEA,0x15,0xD1,0x72,0x1D,0x03, 0xF4,0x82,0xD7,0xCE,0x6E,0x74,0xFE,0xF6,
    0xD5,0x5E,0x70,0x2F,0x46,0x98,0x0C,0x82, 0xB5,0xA8,0x40,0x31,0x90,0x0B,0x1C,0x9E,
    0x59,0xE7,0xC9,0x7F,0xBE,0xC7,0xE8,0xF3, 0x23,0xA9,0x7A,0x7E,0x36,0xCC,0x88,0xBE,
    0x0F,0x1D,0x45,0xB7,0xFF,0x58,0x5A,0xC5, 0x4B,0xD4,0x07,0xB2,0x2B,0x41,0x54,0xAA,
    0xCC,0x8F,0x6D,0x7E,0xBF,0x48,0xE1,0xD8, 0x14,0xCC,0x5E,0xD2,0x0F,0x80,0x37,0xE0,
    0xA7,0x97,0x15,0xEE,0xF2,0x9B,0xE3,0x28, 0x06,0xA1,0xD5,0x8B,0xB7,0xC5,0xDA,0x76,
    0xF5,0x50,0xAA,0x3D,0x8A,0x1F,0xBF,0xF0, 0xEB,0x19,0xCC,0xB1,0xA3,0x13,0xD5,0x5C,
    0xDA,0x56,0xC9,0xEC,0x2E,0xF2,0x96,0x32, 0x38,0x7F,0xE8,0xD7,0x6E,0x3C,0x04,0x68,
    0x04,0x3E,0x8F,0x66,0x3F,0x48,0x60,0xEE, 0x12,0xBF,0x2D,0x5B,0x0B,0x74,0x74,0xD6,
    0xE6,0x94,0xF9,0x1E,0x6D,0xBE,0x11,0x59, 0x74,0xA3,0x92,0x6F,0x12,0xFE,0xE5,0xE4,
    0x38,0x77,0x7C,0xB6,0xA9,0x32,0xDF,0x8C, 0xD8,0xBE,0xC4,0xD0,0x73,0xB9,0x31,0xBA,
    0x3B,0xC8,0x32,0xB6,0x8D,0x9D,0xD3,0x00, 0x74,0x1F,0xA7,0xBF,0x8A,0xFC,0x47,0xED,
    0x25,0x76,0xF6,0x93,0x6B,0xA4,0x24,0x66, 0x3A,0xAB,0x63,0x9C,0x5A,0xE4,0xF5,0x68,
    0x34,0x23,0xB4,0x74,0x2B,0xF1,0xC9,0x78, 0x23,0x8F,0x16,0xCB,0xE3,0x9D,0x65,0x2D,
    0xE3,0xFD,0xB8,0xBE,0xFC,0x84,0x8A,0xD9, 0x22,0x22,0x2E,0x04,0xA4,0x03,0x7C,0x07,
    0x13,0xEB,0x57,0xA8,0x1A,0x23,0xF0,0xC7, 0x34,0x73,0xFC,0x64,0x6C,0xEA,0x30,0x6B,
    0x4B,0xCB,0xC8,0x86,0x2F,0x83,0x85,0xDD, 0xFA,0x9D,0x4B,0x7F,0xA2,0xC0,0x87,0xE8,
    0x79,0x68,0x33,0x03,0xED,0x5B,0xDD,0x3A, 0x06,0x2B,0x3C,0xF5,0xB3,0xA2,0x78,0xA6,
    0x6D,0x2A,0x13,0xF8,0x3F,0x44,0xF8,0x2D, 0xDF,0x31,0x0E,0xE0,0x74,0xAB,0x6A,0x36,
    0x45,0x97,0xE8,0x99,0xA0,0x25,0x5D,0xC1, 0x64,0xF3,0x1C,0xC5,0x08,0x46,0x85,0x1D,
    0xF9,0xAB,0x48,0x19,0x5D,0xED,0x7E,0xA1, 0xB1,0xD5,0x10,0xBD,0x7E,0xE7,0x4D,0x73,
    0xFA,0xF3,0x6B,0xC3,0x1E,0xCF,0xA2,0x68, 0x35,0x90,0x46,0xF4,0xEB,0x87,0x9F,0x92,
    0x40,0x09,0x43,0x8B,0x48,0x1C,0x6C,0xD7, 0x88,0x9A,0x00,0x2E,0xD5,0xEE,0x38,0x2B,
    0xC9,0x19,0x0D,0xA6,0xFC,0x02,0x6E,0x47, 0x95,0x58,0xE4,0x47,0x56,0x77,0xE9,0xAA,
    0x9E,0x30,0x50,0xE2,0x76,0x56,0x94,0xDF, 0xC8,0x1F,0x56,0xE8,0x80,0xB9,0x6E,0x71,
    0x60,0xC9,0x80,0xDD,0x98,0xED,0xD3,0xDF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
];

/// Constructs a [`DhGroup`] for RFC 3526 group14 (2048-bit prime).
///
/// `priv_bits = 256` per RFC 7919 §A — twice the symmetric security level
/// (~AES-128) targeted by a 2048-bit prime.
pub fn group14() -> DhGroup {
    DhGroup {
        name: "group14",
        p: BoxedUint::from_be_bytes(&GROUP14_P),
        g: BoxedUint::from_u64(2),
        priv_bits: 256,
    }
}

/// Constructs a [`DhGroup`] for RFC 3526 group15 (3072-bit prime).
///
/// `priv_bits = 256` (AES-128 equivalent symmetric strength).
pub fn group15() -> DhGroup {
    DhGroup {
        name: "group15",
        p: BoxedUint::from_be_bytes(&GROUP15_P),
        g: BoxedUint::from_u64(2),
        priv_bits: 256,
    }
}

/// Constructs a [`DhGroup`] for RFC 3526 group16 (4096-bit prime).
///
/// `priv_bits = 384` (RFC 7919 §A guidance for ~AES-192 symmetric strength).
pub fn group16() -> DhGroup {
    DhGroup {
        name: "group16",
        p: BoxedUint::from_be_bytes(&GROUP16_P),
        g: BoxedUint::from_u64(2),
        priv_bits: 384,
    }
}

/// Constructs a [`DhGroup`] for RFC 3526 group17 (6144-bit prime).
///
/// `priv_bits = 512` (RFC 7919 §A guidance for ~AES-256 symmetric strength).
pub fn group17() -> DhGroup {
    DhGroup {
        name: "group17",
        p: BoxedUint::from_be_bytes(&GROUP17_P),
        g: BoxedUint::from_u64(2),
        priv_bits: 512,
    }
}

/// Constructs a [`DhGroup`] for RFC 3526 group18 (8192-bit prime).
///
/// `priv_bits = 512` (RFC 7919 §A guidance for ~AES-256 symmetric strength).
pub fn group18() -> DhGroup {
    DhGroup {
        name: "group18",
        p: BoxedUint::from_be_bytes(&GROUP18_P),
        g: BoxedUint::from_u64(2),
        priv_bits: 512,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn group14_constants() {
        let g = group14();
        assert_eq!(g.name(), "group14");
        assert_eq!(g.bit_size(), 2048);
        assert_eq!(g.byte_size(), 256);
        assert!(g.p().is_odd());
        assert_eq!(*g.g(), BoxedUint::from_u64(2));
    }

    #[test]
    fn group15_constants() {
        let g = group15();
        assert_eq!(g.bit_size(), 3072);
        assert!(g.p().is_odd());
    }

    #[test]
    fn group16_constants() {
        let g = group16();
        assert_eq!(g.name(), "group16");
        assert_eq!(g.bit_size(), 4096);
        assert_eq!(g.byte_size(), 512);
        assert!(g.p().is_odd());
        assert_eq!(*g.g(), BoxedUint::from_u64(2));
    }

    #[test]
    fn group17_constants() {
        let g = group17();
        assert_eq!(g.bit_size(), 6144);
        assert!(g.p().is_odd());
    }

    #[test]
    fn group18_constants() {
        let g = group18();
        assert_eq!(g.bit_size(), 8192);
        assert!(g.p().is_odd());
    }

    #[test]
    fn from_custom_validates_g() {
        let p = group14().p().clone();
        // g = 0, 1, p - 1, p are all rejected.
        assert!(DhGroup::from_custom(p.clone(), BoxedUint::from_u64(0), 256).is_err());
        assert!(DhGroup::from_custom(p.clone(), BoxedUint::from_u64(1), 256).is_err());
        let pm1 = p.sub(&BoxedUint::from_u64(1));
        assert!(DhGroup::from_custom(p.clone(), pm1, 256).is_err());
        assert!(DhGroup::from_custom(p.clone(), p.clone(), 256).is_err());
        // g = 2 is valid.
        assert!(DhGroup::from_custom(p.clone(), BoxedUint::from_u64(2), 256).is_ok());
    }

    /// `from_custom` Miller-Rabin-tests the candidate: a composite `p` of
    /// sufficient bit length passes every structural check but must still be
    /// rejected. (The verdict is deterministic — the MR bases derive from the
    /// candidate itself.)
    #[test]
    fn from_custom_rejects_composite_p() {
        // Corrupt one byte in the middle of group14's prime: still odd,
        // still exactly 2048 bits, but composite.
        let mut p_bytes = group14().p().to_be_bytes(256);
        p_bytes[128] ^= 0x02;
        let p = BoxedUint::from_be_bytes(&p_bytes);
        assert_eq!(p.bit_len(), 2048);
        assert!(DhGroup::from_custom(p.clone(), BoxedUint::from_u64(2), 256).is_err());
        // ...while the documented escape hatch still accepts it (the caller
        // owns the primality property there).
        assert!(DhGroup::from_custom_unchecked(p, BoxedUint::from_u64(2), 256).is_ok());
    }

    #[test]
    fn from_custom_rejects_even_p() {
        // Probe the even-`p` check via the unchecked variant — `from_custom`
        // would reject the toy 4-bit modulus on size first.
        let even = BoxedUint::from_u64(10);
        assert!(DhGroup::from_custom_unchecked(even, BoxedUint::from_u64(2), 256).is_err());
    }

    /// `from_custom` enforces `MIN_CUSTOM_GROUP_BITS` (2048). A 2047-bit `p`
    /// is rejected; the unchecked variant accepts the same input subject only
    /// to the structural checks. group14 (2048 bits exactly) is the threshold.
    #[test]
    fn from_custom_enforces_min_bits() {
        // Build a 2047-bit odd `p` by clearing the top bit of group14's
        // 2048-bit prime and OR'ing in 1 to keep it odd. We don't care about
        // primality here — only that the bit-length gate fires.
        let mut p_bytes = group14().p().to_be_bytes(256);
        p_bytes[0] &= 0x7f; // clear MSB → now ≤ 2047 bits
        p_bytes[255] |= 0x01; // keep it odd
        let p_2047 = BoxedUint::from_be_bytes(&p_bytes);
        assert!(p_2047.bit_len() <= 2047);
        assert!(DhGroup::from_custom(p_2047.clone(), BoxedUint::from_u64(2), 256).is_err());
        assert!(DhGroup::from_custom_unchecked(p_2047, BoxedUint::from_u64(2), 256).is_ok());
        // group14 (2048 bits exactly) is accepted.
        assert!(DhGroup::from_custom(group14().p().clone(), BoxedUint::from_u64(2), 256).is_ok());
    }
}
