Files
serai/networks/monero/generators/src/hash_to_point.rs
Luke Parker c3472b4ceb Response to usage of unwrap in non-test code
This commit replaces all usage of `unwrap` with `expect` within
`networks/monero`, clarifying why the panic risked is unreachable. This commit
also replaces some uses of `unwrap` with solutions which are guaranteed not to
fail.

Notably, compilation on 128-bit systems is prevented, ensuring
`u64::try_from(usize::MAX)` will never panic at runtime.

Slight breaking changes are additionally included as necessary to massage out
some avoidable panics.
2025-08-08 21:33:28 -04:00

72 lines
2.3 KiB
Rust

use subtle::ConditionallySelectable;
use curve25519_dalek::edwards::EdwardsPoint;
use group::ff::{Field, PrimeField};
use dalek_ff_group::FieldElement;
use monero_io::decompress_point;
use crate::keccak256;
/// Monero's `hash_to_ec` function.
pub fn hash_to_point(bytes: [u8; 32]) -> EdwardsPoint {
#[allow(non_snake_case)]
let A = FieldElement::from(486662u64);
let v = FieldElement::from_square(keccak256(&bytes)).double();
let w = v + FieldElement::ONE;
let x = w.square() + (-A.square() * v);
// This isn't the complete X, yet its initial value
// We don't calculate the full X, and instead solely calculate Y, letting dalek reconstruct X
// While inefficient, it solves API boundaries and reduces the amount of work done here
#[allow(non_snake_case)]
let X = {
let u = w;
let v = x;
let v3 = v * v * v;
let uv3 = u * v3;
let v7 = v3 * v3 * v;
let uv7 = u * v7;
uv3 *
uv7.pow(
(-FieldElement::from(5u8)) *
FieldElement::from(8u8).invert().expect("eight was coprime with the prime 2^{255}-19"),
)
};
let x = X.square() * x;
let y = w - x;
let non_zero_0 = !y.is_zero();
let y_if_non_zero_0 = w + x;
let sign = non_zero_0 & (!y_if_non_zero_0.is_zero());
let mut z = -A;
z *= FieldElement::conditional_select(&v, &FieldElement::from(1u8), sign);
#[allow(non_snake_case)]
let Z = z + w;
#[allow(non_snake_case)]
let mut Y = z - w;
/*
If sign, `z = -486662`, else, `z = -486662 * v`
`w = v + 1`
We need `z + w \ne 0`, which would require `z \cong -w \mod 2^{255}-19`. This requires:
- If `sign`, `v \mod 2^{255}-19 \ne 486661`.
- If `!sign`, `(v + 1) \mod 2^{255}-19 \ne (v * 486662) \mod 2^{255}-19` which is equivalent to
`(v * 486661) \mod 2^{255}-19 \ne 1`.
In summary, if `sign`, `v` must not `486661`, and if `!sign`, `v` must not be the
multiplicative inverse of `486661`. Since `v` is the output of a hash function, this should
have negligible probability. Additionally, since the definition of `sign` is dependent on `v`,
it may be truly impossible to reach.
*/
Y *= Z.invert().expect("if sign, v was 486661. if !sign, v was 486661^{-1}");
let mut bytes = Y.to_repr();
bytes[31] |= sign.unwrap_u8() << 7;
decompress_point(bytes).expect("point from hash-to-curve wasn't on-curve").mul_by_cofactor()
}