use alloc::vec;
use alloc::vec::Vec;
use curve25519_dalek::edwards::{CompressedEdwardsY, EdwardsPoint};
use sha2::{Digest, Sha512};
use crate::common::{CryptoError, CryptoResult, ONE, SUITE_DRAFT03};
#[must_use]
pub fn cardano_clear_cofactor(point: &EdwardsPoint) -> EdwardsPoint {
point.mul_by_cofactor()
}
pub fn cardano_hash_to_curve(pk: &[u8], message: &[u8]) -> CryptoResult<(EdwardsPoint, [u8; 32])> {
let mut hasher = Sha512::new();
hasher.update([SUITE_DRAFT03]);
hasher.update([ONE]);
hasher.update(pk);
hasher.update(message);
let r_hash = hasher.finalize();
let mut r_bytes = [0u8; 32];
r_bytes.copy_from_slice(&r_hash[0..32]);
use super::elligator2::elligator2_to_edwards;
match elligator2_to_edwards(&r_bytes) {
Some(point) => {
let compressed = point.compress();
Ok((point, compressed.0))
}
None => {
Err(CryptoError::InvalidPoint)
}
}
}
fn expand_message_xmd(dst: &[u8], msg: &[u8], len_in_bytes: usize) -> Vec<u8> {
const B_IN_BYTES: usize = 64; const R_IN_BYTES: usize = 128;
let ell = len_in_bytes.div_ceil(B_IN_BYTES);
let mut dst_prime = dst.to_vec();
dst_prime.push(dst.len() as u8);
let z_pad = vec![0u8; R_IN_BYTES];
let l_i_b_str = [(len_in_bytes >> 8) as u8, (len_in_bytes & 0xFF) as u8];
let mut hasher = Sha512::new();
hasher.update(z_pad);
hasher.update(msg);
hasher.update(l_i_b_str);
hasher.update([0u8]);
hasher.update(&dst_prime);
let b_0 = hasher.finalize();
let mut hasher = Sha512::new();
hasher.update(b_0);
hasher.update([1u8]);
hasher.update(&dst_prime);
let mut b_i = hasher.finalize();
let mut uniform_bytes = b_i.to_vec();
for i in 2..=ell {
let mut hasher = Sha512::new();
let mut xor_result = [0u8; B_IN_BYTES];
for j in 0..B_IN_BYTES {
xor_result[j] = b_0[j] ^ b_i[j];
}
hasher.update(xor_result);
hasher.update([i as u8]);
hasher.update(&dst_prime);
b_i = hasher.finalize();
uniform_bytes.extend_from_slice(&b_i);
}
uniform_bytes.truncate(len_in_bytes);
uniform_bytes
}
pub fn cardano_hash_to_curve_draft13(
pk: &[u8],
message: &[u8],
) -> CryptoResult<(EdwardsPoint, [u8; 48])> {
let mut input = Vec::with_capacity(pk.len() + message.len());
input.extend_from_slice(pk);
input.extend_from_slice(message);
let dst = b"ECVRF_edwards25519_XMD:SHA-512_ELL2_NU_\x04";
let expanded = expand_message_xmd(dst, &input, 48);
let mut h_string = [0u8; 48];
h_string.copy_from_slice(&expanded);
let mut point_bytes = [0u8; 32];
point_bytes.copy_from_slice(&h_string[0..32]);
point_bytes[31] &= 0x7f;
match CompressedEdwardsY(point_bytes).decompress() {
Some(point) => {
let cleared = cardano_clear_cofactor(&point);
Ok((cleared, h_string))
}
None => {
for i in 0..=255u8 {
let mut retry_hasher = Sha512::new();
retry_hasher.update(point_bytes);
retry_hasher.update([i]);
let retry_hash = retry_hasher.finalize();
let mut retry_bytes = [0u8; 32];
retry_bytes.copy_from_slice(&retry_hash[0..32]);
retry_bytes[31] &= 0x7f;
if let Some(point) = CompressedEdwardsY(retry_bytes).decompress() {
let cleared = cardano_clear_cofactor(&point);
h_string[0..32].copy_from_slice(&retry_bytes);
return Ok((cleared, h_string));
}
}
Err(CryptoError::InvalidPoint)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cofactor_clearing() {
use curve25519_dalek::constants::ED25519_BASEPOINT_POINT;
let point = ED25519_BASEPOINT_POINT;
let cleared = cardano_clear_cofactor(&point);
assert!(cleared.is_torsion_free());
}
#[test]
fn test_hash_to_curve() {
let pk = [0u8; 32];
let message = b"test";
let result = cardano_hash_to_curve(&pk, message);
assert!(result.is_ok());
}
#[test]
fn test_hash_to_curve_draft13() {
let pk = [0u8; 32];
let message = b"test";
let result = cardano_hash_to_curve_draft13(&pk, message);
assert!(result.is_ok());
if let Ok((_, h_string)) = result {
assert_eq!(h_string.len(), 48);
}
}
}