midnight-curves 0.2.0

Implementation of BLS12 381 and Jubjub curves.
Documentation
use ff::{Field, PrimeField};
use group::{prime::PrimeCurveAffine, Curve, Group, GroupEncoding};
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};

use super::{fp::Fp, fq::Fq as Secp256k1Scalar};
use crate::{
    curve::{Coordinates, CurveAffine, CurveExt},
    serde::CompressedFlagConfig,
};

// Reference: https://neuromancer.sk/std/secg/secp256k1
const SECP_GENERATOR_X: Fp = Fp::from_raw([
    0x59F2815B16F81798,
    0x029BFCDB2DCE28D9,
    0x55A06295CE870B07,
    0x79BE667EF9DCBBAC,
]);
const SECP_GENERATOR_Y: Fp = Fp::from_raw([
    0x9C47D08FFB10D4B8,
    0xFD17B448A6855419,
    0x5DA4FBFC0E1108A8,
    0x483ADA7726A3C465,
]);

const SECP_A: Fp = Fp::from_raw([0, 0, 0, 0]);
const SECP_B: Fp = Fp::from_raw([7, 0, 0, 0]);

impl group::cofactor::CofactorGroup for Secp256k1 {
    type Subgroup = Secp256k1;

    fn clear_cofactor(&self) -> Self {
        *self
    }

    fn into_subgroup(self) -> CtOption<Self::Subgroup> {
        CtOption::new(self, 1.into())
    }

    fn is_torsion_free(&self) -> Choice {
        1.into()
    }
}

crate::new_curve_impl!(
    (pub),
    Secp256k1,
    Secp256k1Affine,
    Fp,
    Secp256k1Scalar,
    (
    SECP_GENERATOR_X,
    SECP_GENERATOR_Y
    ),
    SECP_A,
    SECP_B,
    "secp256k1",
    |domain_prefix| hash_to_curve(domain_prefix, hash_to_curve_suite(b"secp256k1_XMD:SHA-256_SSWU_RO_")),
    CompressedFlagConfig::Extra,
    standard_sign
);

fn hash_to_curve_suite(domain: &[u8]) -> crate::hash_to_curve::Suite<Secp256k1, sha2::Sha256, 48> {
    use crate::hash_to_curve::{Iso, Method};

    // Z = -11 (reference: <https://www.rfc-editor.org/rfc/rfc9380.html#name-suites-for-secp256k1>)
    // 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc24
    const SSWU_Z: Fp = Fp::from_raw([
        0xfffffffefffffc24,
        0xffffffffffffffff,
        0xffffffffffffffff,
        0xffffffffffffffff,
    ]);

    // E': y'^2 = x'^3 + A' * x' + B', where
    // A': 0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533
    // B': 1771
    // (reference: <https://www.rfc-editor.org/rfc/rfc9380.html#name-suites-for-secp256k1>)
    const ISO_SECP_A: Fp = Fp::from_raw([
        0x405447c01a444533,
        0xe953d363cb6f0e5d,
        0xa08a5558f0f5d272,
        0x3f8731abdd661adc,
    ]);

    const ISO_SECP_B: Fp = Fp::from_raw([1771, 0, 0, 0]);

    let iso_map = Iso {
        a: ISO_SECP_A,
        b: ISO_SECP_B,
        map: Box::new(iso_map),
    };

    crate::hash_to_curve::Suite::new(domain, SSWU_Z, Method::SSWU(iso_map))
}

#[allow(clippy::type_complexity)]
fn hash_to_curve<'a>(
    domain_prefix: &'a str,
    suite: crate::hash_to_curve::Suite<Secp256k1, sha2::Sha256, 48>,
) -> Box<dyn Fn(&[u8]) -> Secp256k1 + 'a> {
    Box::new(move |message| suite.hash_to_curve(domain_prefix, message))
}

/// 3-Isogeny Map for Secp256k1
/// Reference: <https://www.rfc-editor.org/rfc/rfc9380.html#name-3-isogeny-map-for-secp256k1>
fn iso_map(x: Fp, y: Fp, z: Fp) -> Secp256k1 {
    // constants for secp256k1 iso_map computation
    const K: [[Fp; 4]; 5] = [
        [Fp::ZERO; 4],
        [
            Fp::from_raw([
                0x8e38e38daaaaa8c7,
                0x38e38e38e38e38e3,
                0xe38e38e38e38e38e,
                0x8e38e38e38e38e38,
            ]),
            Fp::from_raw([
                0xdfff1044f17c6581,
                0xd595d2fc0bf63b92,
                0xb9f315cea7fd44c5,
                0x7d3d4c80bc321d5,
            ]),
            Fp::from_raw([
                0x4ecbd0b53d9dd262,
                0xe4506144037c4031,
                0xe2a413deca25caec,
                0x534c328d23f234e6,
            ]),
            Fp::from_raw([
                0x8e38e38daaaaa88c,
                0x38e38e38e38e38e3,
                0xe38e38e38e38e38e,
                0x8e38e38e38e38e38,
            ]),
        ],
        [
            Fp::from_raw([
                0x9fe6b745781eb49b,
                0x86cd409542f8487d,
                0x9ca34ccbb7b640dd,
                0xd35771193d94918a,
            ]),
            Fp::from_raw([
                0xc52a56612a8c6d14,
                0x06d36b641f5e41bb,
                0xf7c4b2d51b542254,
                0xedadc6f64383dc1d,
            ]),
            Fp::ZERO,
            Fp::ZERO,
        ],
        [
            Fp::from_raw([
                0xa12f684b8e38e23c,
                0x2f684bda12f684bd,
                0x684bda12f684bda1,
                0x4bda12f684bda12f,
            ]),
            Fp::from_raw([
                0xdffc90fc201d71a3,
                0x647ab046d686da6f,
                0xa9d0a54b12a0a6d5,
                0xc75e0c32d5cb7c0f,
            ]),
            Fp::from_raw([
                0xa765e85a9ecee931,
                0x722830a201be2018,
                0x715209ef6512e576,
                0x29a6194691f91a73,
            ]),
            Fp::from_raw([
                0x84bda12f38e38d84,
                0xbda12f684bda12f6,
                0xa12f684bda12f684,
                0x2f684bda12f684bd,
            ]),
        ],
        [
            Fp::from_raw([
                0xfffffffefffff93b,
                0xffffffffffffffff,
                0xffffffffffffffff,
                0xffffffffffffffff,
            ]),
            Fp::from_raw([
                0xdfb425d2685c2573,
                0x9467c1bfc8e8d978,
                0xd5e9e6632722c298,
                0x7a06534bb8bdb49f,
            ]),
            Fp::from_raw([
                0xa7bf8192bfd2a76f,
                0x0a3d21162f0d6299,
                0xf3a70c3fa8fe337e,
                0x6484aa716545ca2c,
            ]),
            Fp::ZERO,
        ],
    ];

    let z2 = z.square();
    let z3 = z2 * z;

    // iso_map logic (avoid inversion) in projective coordinates
    //   reference: <https://github.com/zcash/pasta_curves/blob/main/src/hashtocurve.rs#L80-L106>
    let x_num = ((K[1][3] * x + K[1][2] * z) * x + K[1][1] * z2) * x + K[1][0] * z3;
    let x_den = (z * x + K[2][1] * z2) * x + K[2][0] * z3;

    let y_num = (((K[3][3] * x + K[3][2] * z) * x + K[3][1] * z2) * x + K[3][0] * z3) * y;
    let y_den = (((x + K[4][2] * z) * x + K[4][1] * z2) * x + K[4][0] * z3) * z;

    let z = x_den * y_den;
    let x = x_num * y_den;
    let y = y_num * x_den;

    Secp256k1 { x, y, z }
}

#[cfg(test)]
mod tests {
    use ff::Field;

    use super::*;

    #[test]
    fn test_curve_equation() {
        // Test that the generator is on the curve
        let g = Secp256k1::generator();
        assert!(bool::from(g.is_on_curve()));
    }

    #[test]
    fn test_generator_coordinates() {
        let g_affine = Secp256k1::generator().to_affine();
        let coords = g_affine.coordinates().unwrap();

        // Expected generator x coordinate
        let expected_x = Fp::from_raw([
            0x59F2815B16F81798,
            0x029BFCDB2DCE28D9,
            0x55A06295CE870B07,
            0x79BE667EF9DCBBAC,
        ]);

        // Expected generator y coordinate
        let expected_y = Fp::from_raw([
            0x9C47D08FFB10D4B8,
            0xFD17B448A6855419,
            0x5DA4FBFC0E1108A8,
            0x483ADA7726A3C465,
        ]);

        assert_eq!(*coords.x(), expected_x);
        assert_eq!(*coords.y(), expected_y);
    }

    #[test]
    fn test_curve_constants() {
        // a = 0
        assert_eq!(Secp256k1::a(), Fp::ZERO);

        // b = 7
        let expected_b = Fp::from_raw([
            0x0000000000000007,
            0x0000000000000000,
            0x0000000000000000,
            0x0000000000000000,
        ]);
        assert_eq!(Secp256k1::b(), expected_b);
    }

    #[test]
    fn test_hash_to_curve() {
        use std::convert::TryInto;
        // Test vectors from RFC 9380
        // https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-expand_message_xmdsha-256
        let test_cases: &[(&[u8], &str, &str)] = &[
            (
                b"" as &[u8],
                "c1cae290e291aee617ebaef1be6d73861479c48b841eaba9b7b5852ddfeb1346",
                "64fa678e07ae116126f08b022a94af6de15985c996c3a91b64c406a960e51067",
            ),
            (
                b"abc" as &[u8],
                "3377e01eab42db296b512293120c6cee72b6ecf9f9205760bd9ff11fb3cb2c4b",
                "7f95890f33efebd1044d382a01b1bee0900fb6116f94688d487c6c7b9c8371f6",
            ),
            (
                b"abcdef0123456789" as &[u8],
                "bac54083f293f1fe08e4a70137260aa90783a5cb84d3f35848b324d0674b0e3a",
                "4436476085d4c3c4508b60fcf4389c40176adce756b398bdee27bca19758d828",
            ),
            (
                b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq" as &[u8],
                "e2167bc785333a37aa562f021f1e881defb853839babf52a7f72b102e41890e9",
                "f2401dd95cc35867ffed4f367cd564763719fbc6a53e969fb8496a1e6685d873",
            ),
            (
                b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" as &[u8],
                "e3c8d35aaaf0b9b647e88a0a0a7ee5d5bed5ad38238152e4e6fd8c1f8cb7c998",
                "8446eeb6181bf12f56a9d24e262221cc2f0c4725c7e3803024b5888ee5823aa6",
            ),
        ];

        for (msg, expected_x_hex, expected_y_hex) in test_cases.iter() {
            let hash_fn = hash_to_curve(
                "QUUX-V01-CS02-with-",
                hash_to_curve_suite(b"secp256k1_XMD:SHA-256_SSWU_RO_"),
            );
            let point = hash_fn(msg).to_affine();
            let coords = point.coordinates().unwrap();

            let mut expected_x_bytes = hex::decode(expected_x_hex).unwrap();
            let mut expected_y_bytes = hex::decode(expected_y_hex).unwrap();

            // Hex values are big-endian, but secp256k1 uses little-endian representation
            expected_x_bytes.reverse();
            expected_y_bytes.reverse();

            let expected_x_array: [u8; 32] = expected_x_bytes.as_slice().try_into().unwrap();
            let expected_y_array: [u8; 32] = expected_y_bytes.as_slice().try_into().unwrap();

            let expected_x = Fp::from_repr(expected_x_array.into()).unwrap();
            let expected_y = Fp::from_repr(expected_y_array.into()).unwrap();

            assert_eq!(
                *coords.x(),
                expected_x,
                "x coordinate mismatch for message: {:?}",
                msg
            );
            assert_eq!(
                *coords.y(),
                expected_y,
                "y coordinate mismatch for message: {:?}",
                msg
            );
        }
    }
}