Skip to content

bignp256: SWU #1734

@makavity

Description

@makavity

Hello.
I'm implementing the ⁠bake-swu protocol according to STB 34.101.66-2014
The algorithm uses ⁠belt-kwp (Belt Key Wrap) as the expand function instead of a traditional hash function. This means I cannot use ⁠OsswuMap directly, but the algorithm itself is compatible with the ⁠map2curve interface.

Image
pub struct BeltKwpExpander {
    /// Output buffer containing the 48-byte result of belt-keywrap
    buf: [u8; 48],
    /// Current position in the buffer for reading
    offset: usize,
}

impl Expander for BeltKwpExpander { ... }
impl ExpandMsg<'_> for BeltKwpExpander { ... }

impl FieldElement {
    /// Reduces a 384-bit (48-byte) value to a field element modulo p.
    pub(crate) fn reduce_bytes_to_field(bytes: &GenericArray<u8, U48>) -> Self {
        let value = U384::from_le_slice(bytes);

        let modulus = {
            let mut modulus_bytes = [0u8; 48];
            modulus_bytes[..32].copy_from_slice(&MODULUS.to_le_bytes());
            U384::from_le_bytes(modulus_bytes)
        };

        let reduced = value.const_rem(&modulus).0;
        let reduced_bytes = reduced.to_le_bytes();
        let result = U256::from_le_slice(&reduced_bytes[..32]);

        Self::from_uint_unchecked(result)
    }

    /// Simplified SWU map from field element to curve point.
    ///
    /// Implements the bake-swu algorithm from STB 34.101.66-2014, section 6.2.3.
    /// Input `self` is the field element s obtained after belt-keywrap preprocessing.
    #[allow(clippy::arithmetic_side_effects)]
    pub(crate) fn swu(&self) -> AffinePoint {
        // Step 3: t ← -s² mod p
        let t = self.square().neg();
        let t_squared = t.square();

        // Step 4: Compute numerator = (1 + t + t²) and denominator = a(t + t²)
        let num = FieldElement::ONE + t + t_squared;
        let den = BignP256::EQUATION_A * (t + t_squared);

        // Step 4: x₁ ← -b(1 + t + t²)(a(t + t²))^(-1) mod p
        let x1 = -BignP256::EQUATION_B * num * den.invert().unwrap();

        // Step 5: x₂ ← t·x₁ mod p
        let x2 = t * x1;

        // Step 6: y ← (x₁³ + a·x₁ + b) mod p
        let gx1 = x1.cube() + BignP256::EQUATION_A * x1 + BignP256::EQUATION_B;

        // Steps 7-8: Compute square root and check if gx1 is a quadratic residue
        let (is_square, y1) = gx1.sqrt_alt();

        // Step 7: s³·y mod p (for alternative case)
        let y2 = self.cube() * y1;

        // Step 9: Constant-time selection of final coordinates
        let x = FieldElement::conditional_select(&x2, &x1, is_square);
        let y = FieldElement::conditional_select(&y2, &y1, is_square);

        // Step 10: Return W as AffinePoint
        let point = EncodedPoint::from_affine_coordinates(&x.to_bytes(), &y.to_bytes(), false);
        AffinePoint::from_encoded_point(&point).unwrap()
    }
}

impl MapToCurve for FieldElement {
    type Output = ProjectivePoint;

    fn map_to_curve(&self) -> Self::Output {
        self.swu().into()
    }
}

impl FromOkm for FieldElement {
    type Length = U48;

    fn from_okm(data: &GenericArray<u8, Self::Length>) -> Self {
        Self::reduce_bytes_to_field(data)
    }
}

impl GroupDigest for BignP256 {
    type FieldElement = FieldElement;
}

impl BignP256 {
    pub fn hash_secret_to_curve(secret: &[u8]) -> elliptic_curve::Result<ProjectivePoint> {
        use crate::arithmetic::expand::BeltKwpExpander;
        use elliptic_curve::{hash2curve::ExpandMsg, hash2curve::Expander};

        // 1. H ← belt-keywrap(X, 0^128, 0^256)
        // Expand input secret to 48 bytes (384 bits) using Belt-KWP
        let mut expander = BeltKwpExpander::expand_message(&[secret], &[], 48)?;

        // 2. s ← H mod p
        // Extract 48-byte output and reduce modulo field prime
        let mut okm = GenericArray::<u8, U48>::default();
        expander.fill_bytes(&mut okm);
        let field_elem = FieldElement::from_okm(&okm);

        // 3-10. Apply simplified SWU mapping
        let point = field_elem.map_to_curve();

        Ok(point)
    }
}

It is the backport on 0.13 version, for example.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions