libecvrf 1.2.1-beta.0

A ECVRF that's friendly with EVM, based on curve secp256k1 and keccak256
Documentation
use crate::helper::FIELD_SIZE;
use tiny_ec::{
    curve::{Affine, Field, Jacobian, Scalar},
    ECMULT_GEN_CONTEXT,
};
use tiny_keccak::{Hasher, Keccak};

/// Try to generate a point on the curve based on hashes
pub fn new_candidate_point(b: &[u8]) -> Affine {
    // X is a digest of field
    let mut x = field_hash(b);
    // Y is a coordinate point, corresponding to x
    let (mut y, _) = y_squared(&x).sqrt();
    x.normalize();
    y.normalize();

    if y.is_odd() {
        y = y.neg(1);
        y.normalize();
    }
    Affine::new(x, y)
}

/// Y squared, it was calculate by evaluate X
pub fn y_squared(x: &Field) -> Field {
    let mut t = *x;
    // y^2 = x^3 + 7
    t = t * t * t + Field::from_int(7);
    t.normalize();
    t
}

/// Check point is on curve or not
pub fn is_on_curve(point: &Affine) -> bool {
    y_squared(&point.x) == point.y * point.y
}

/// Hash to curve with prefix
/// HASH_TO_CURVE_HASH_PREFIX = 1
pub fn hash_to_curve_prefix(alpha: &Scalar, pk: &Affine) -> Affine {
    let mut tpk = *pk;
    tpk.x.normalize();
    tpk.y.normalize();
    let packed = [
        // HASH_TO_CURVE_HASH_PREFIX = 1
        Field::from_int(1).b32().to_vec(),
        // pk
        tpk.x.b32().to_vec(),
        tpk.y.b32().to_vec(),
        // seed
        alpha.b32().to_vec(),
    ]
    .concat();
    let mut rv = new_candidate_point(&packed);
    while !is_on_curve(&rv) {
        rv = new_candidate_point(rv.x.b32().as_ref());
    }
    rv
}

/// Hash bytes array to a field
pub fn field_hash(b: &[u8]) -> Field {
    let mut output = [0u8; 32];
    let mut hasher = Keccak::v256();
    hasher.update(b);
    hasher.finalize(&mut output);
    let mut s = Scalar::from(&output);
    if s >= FIELD_SIZE {
        let mut hasher = Keccak::v256();
        hasher.update(&output);
        hasher.finalize(&mut output);
        assert!(bool::from(s.set_b32(&output)), "Unable to set field");
    }
    let mut f = Field::default();
    if !f.set_b32(&s.b32()) {
        f.normalize();
    }
    f
}

/// Hash point to Scalar
pub fn hash_points(
    g: &Affine,
    h: &Affine,
    pk: &Affine,
    gamma: &Affine,
    kg: &Affine,
    kh: &Affine,
) -> Scalar {
    let mut output = [0u8; 32];
    let mut hasher = Keccak::v256();
    let all_points = [g, h, pk, gamma, kg, kh];
    for point in all_points {
        hasher.update(point.x.b32().as_ref());
        hasher.update(point.y.b32().as_ref());
    }
    hasher.finalize(&mut output);
    Scalar::from(&output)
}

/// Hash points with prefix
/// SCALAR_FROM_CURVE_POINTS_HASH_PREFIX = 2
pub fn hash_points_prefix(
    hash: &Affine,
    pk: &Affine,
    gamma: &Affine,
    u_witness: &[u8; 20],
    v: &Affine,
) -> Scalar {
    let mut output = [0u8; 32];
    let mut hasher = Keccak::v256();
    let all_points = [hash, pk, gamma, v];
    // SCALAR_FROM_CURVE_POINTS_HASH_PREFIX = 2
    hasher.update(Scalar::from_int(2).b32().as_ref());
    for point in all_points {
        hasher.update(point.x.b32().as_ref());
        hasher.update(point.y.b32().as_ref());
    }
    hasher.update(u_witness);
    hasher.finalize(&mut output);
    Scalar::from(&output)
}

/// Hash to curve
pub fn hash_to_curve(alpha: &Scalar, y: Option<&Affine>) -> Affine {
    let mut r = Jacobian::default();
    ECMULT_GEN_CONTEXT.ecmult_gen(&mut r, alpha);
    match y {
        Some(v) => {
            r = r.add_ge(v);
            r
        }
        None => r,
    };
    Affine::from(&r)
}

#[cfg(test)]
mod tests {
    use crate::{
        hash::{is_on_curve, new_candidate_point},
        helper::random_bytes,
    };
    use tiny_ec::curve::Scalar;

    #[test]
    fn point_must_be_on_curve() {
        let mut buf = [0u8; 32];
        random_bytes(&mut buf);
        let mut rv = new_candidate_point(buf.as_ref());
        while !is_on_curve(&rv) {
            rv = new_candidate_point(&rv.x.b32());
        }
        assert!(is_on_curve(&rv));
    }

    #[test]
    fn test_scalar_is_gte() {
        let data_set = [
            Scalar([0, 1, 1, 1, 1, 1, 1, 0]),
            Scalar([1, 0, 0, 0, 0, 0, 0, 1]),
            Scalar([0, 1, 1, 1, 1, 1, 0, 1]),
            Scalar([1, 0, 0, 0, 0, 0, 1, 0]),
            Scalar([0, 1, 1, 1, 1, 1, 0, 1]),
            Scalar([0, 1, 1, 1, 1, 1, 0, 1]),
        ];
        let require_output = [
            true, false, false, true, false, false, true, true, false, true, false, false, true,
            true, true, true, true, true, false, false, false, true, false, false, true, true,
            true, true, true, true, true, true, true, true, true, true,
        ];
        for x in 0..data_set.len() {
            for y in 0..data_set.len() {
                assert!(
                    (data_set[x] >= data_set[y]) == require_output[x * data_set.len() + y],
                    "scalar_is_gte() is broken"
                );
            }
        }
    }

    #[test]
    fn test_scalar_is_gt() {
        let data_set = [
            Scalar([0, 1, 1, 1, 1, 1, 0, 1]),
            Scalar([0, 1, 1, 1, 1, 1, 0, 1]),
            Scalar([1, 1, 1, 1, 1, 1, 1, 1]),
            Scalar([0, 0, 0, 0, 0, 0, 0, 0]),
            Scalar([1, 1, 1, 1, 1, 1, 1, 1]),
            Scalar([0, 0, 0, 0, 0, 0, 0, 2]),
            Scalar([1, 1, 1, 1, 1, 1, 1, 1]),
            Scalar([0, 0, 0, 0, 0, 1, 1, 1]),
            Scalar([0, 1, 1, 1, 1, 1, 1, 1]),
            Scalar([1, 1, 1, 1, 1, 1, 1, 1]),
        ];
        let require_output = [
            false, false, false, true, false, false, false, false, false, false, false, false,
            false, true, false, false, false, false, false, false, true, true, false, true, false,
            false, false, true, true, false, false, false, false, false, false, false, false,
            false, false, false, true, true, false, true, false, false, false, true, true, false,
            true, true, true, true, true, false, true, true, true, true, true, true, false, true,
            false, false, false, true, true, false, true, true, false, true, false, false, false,
            false, false, false, true, true, false, true, false, false, false, true, false, false,
            true, true, false, true, false, false, false, true, true, false,
        ];

        for x in 0..data_set.len() {
            for y in 0..data_set.len() {
                assert!(
                    data_set[x].gt(&data_set[y]) == require_output[x * data_set.len() + y],
                    "scalar_is_gt() is broken"
                );
            }
        }
    }
}