use crate::nist::arith::Field;
use crate::nist::point::{AffinePoint, Curve, Point};
use crate::p256::{Fn as Scalar, Fp, P256};
use ic_core::ct::Choice;
use ic_core::traits::internal::Digest;
use ic_core::{ensure, Result, Zeroize};
use ic_hash::Sha256;
const B_IN_BYTES: usize = 32;
const S_IN_BYTES: usize = 64;
const L: usize = 48;
pub fn expand_message_xmd(msg: &[u8], dst: &[u8], out: &mut [u8]) -> Result<()> {
let len = out.len();
let ell = len.div_ceil(B_IN_BYTES);
ensure!(
ell <= 255 && len <= 65535 && !out.is_empty(),
InvalidLength,
"expand_message_xmd output length"
);
ensure!(
dst.len() <= 255,
InvalidParameter,
"expand_message_xmd DST longer than 255 bytes"
);
let dst_len = [dst.len() as u8];
let mut h = Sha256::new();
h.update(&[0u8; S_IN_BYTES]);
h.update(msg);
h.update(&(len as u16).to_be_bytes());
h.update(&[0]);
h.update(dst);
h.update(&dst_len);
let b0 = h.finalize();
let mut previous = [0u8; B_IN_BYTES];
for (i, chunk) in out.chunks_mut(B_IN_BYTES).enumerate() {
let mut input = [0u8; B_IN_BYTES];
for ((slot, a), b) in input.iter_mut().zip(b0.as_ref()).zip(previous.iter()) {
*slot = if i == 0 { *a } else { a ^ b };
}
let mut h = Sha256::new();
h.update(&input);
h.update(&[(i + 1) as u8]);
h.update(dst);
h.update(&dst_len);
let bi = h.finalize();
previous.copy_from_slice(bi.as_ref());
chunk.copy_from_slice(&previous[..chunk.len()]);
input.zeroize();
}
previous.zeroize();
Ok(())
}
fn reduce_wide<F: Field<Bytes = [u8; 32]>>(bytes: &[u8]) -> F {
let mut lo = [0u8; 32];
lo.copy_from_slice(&bytes[16..48]);
let mut hi = [0u8; 32];
hi[16..].copy_from_slice(&bytes[..16]);
let mut two128 = [0u8; 32];
two128[15] = 1;
let r = F::from_bytes_reduced(&two128).square();
let v = F::from_bytes_reduced(&hi)
.mul(&r)
.add(&F::from_bytes_reduced(&lo));
lo.zeroize();
hi.zeroize();
v
}
pub(crate) fn hash_to_scalar(msg: &[&[u8]], dst: &[u8]) -> Result<Scalar> {
let mut uniform = [0u8; L];
expand_parts(msg, dst, &mut uniform)?;
let s = reduce_wide::<Scalar>(&uniform);
uniform.zeroize();
Ok(s)
}
fn expand_parts(msg: &[&[u8]], dst: &[u8], out: &mut [u8]) -> Result<()> {
let total: usize = msg.iter().map(|p| p.len()).sum();
let mut joined = [0u8; 1024];
ensure!(
total <= joined.len(),
InvalidLength,
"hash_to_field message too long"
);
let mut at = 0;
for part in msg {
joined[at..at + part.len()].copy_from_slice(part);
at += part.len();
}
let r = expand_message_xmd(&joined[..total], dst, out);
joined.zeroize();
r
}
fn map_to_curve(u: &Fp) -> AffinePoint<P256> {
let small = |n: u64| Fp::to_mont([n, 0, 0, 0]);
let a = small(3).neg();
let b = P256::B;
let z = small(10).neg();
let c1 = b.neg().mul(&a.invert());
let c2 = b.mul(&z.mul(&a).invert());
let zu2 = z.mul(&u.square());
let tv1 = zu2.square().add(&zu2).invert(); let mut x1 = c1.mul(&Fp::ONE.add(&tv1));
Fp::cmov(&mut x1, &c2, tv1.is_zero());
let g = |x: &Fp| x.square().mul(x).add(&a.mul(x)).add(&b);
let gx1 = g(&x1);
let x2 = zu2.mul(&x1);
let gx2 = g(&x2);
let y1 = P256::sqrt(&gx1);
let y2 = P256::sqrt(&gx2);
let gx1_square = y1.square().ct_eq(&gx1);
let mut x = x2;
let mut y = y2;
Fp::cmov(&mut x, &x1, gx1_square);
Fp::cmov(&mut y, &y1, gx1_square);
let differ = Choice::from_u8(u.is_odd().unwrap_u8() ^ y.is_odd().unwrap_u8());
let neg = y.neg();
Fp::cmov(&mut y, &neg, differ);
AffinePoint { x, y }
}
pub(crate) fn hash_to_curve_point(msg: &[&[u8]], dst: &[u8]) -> Result<Point<P256>> {
let mut uniform = [0u8; 2 * L];
expand_parts(msg, dst, &mut uniform)?;
let u0: Fp = reduce_wide(&uniform[..L]);
let u1: Fp = reduce_wide(&uniform[L..]);
uniform.zeroize();
let q0 = Point::from_affine(&map_to_curve(&u0));
let q1 = Point::from_affine(&map_to_curve(&u1));
Ok(q0.add(&q1))
}
pub fn hash_to_curve(msg: &[u8], dst: &[u8]) -> Result<[u8; 65]> {
ic_core::module::operational()?;
let p = hash_to_curve_point(&[msg], dst)?;
let affine = p.to_affine().ok_or(ic_core::err!(
InvalidParameter,
"hash_to_curve reached the identity"
))?;
let mut out = [0u8; 65];
let _ = affine.write_uncompressed(&mut out);
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
fn h(s: &str) -> [u8; 32] {
let mut out = [0u8; 32];
ic_core::codec::hex_decode(s.as_bytes(), &mut out).unwrap();
out
}
const DST: &[u8] = b"QUUX-V01-CS02-with-P256_XMD:SHA-256_SSWU_RO_";
#[test]
fn each_stage_matches_rfc_9380_for_the_empty_message() {
let mut uniform = [0u8; 2 * L];
expand_message_xmd(b"", DST, &mut uniform).unwrap();
let u0: Fp = reduce_wide(&uniform[..L]);
let u1: Fp = reduce_wide(&uniform[L..]);
assert_eq!(
u0.to_bytes(),
h("ad5342c66a6dd0ff080df1da0ea1c04b96e0330dd89406465eeba11582515009")
);
assert_eq!(
u1.to_bytes(),
h("8c0f1d43204bd6f6ea70ae8013070a1518b43873bcd850aafa0a9e220e2eea5a")
);
let q0 = map_to_curve(&u0);
assert_eq!(
q0.x.to_bytes(),
h("ab640a12220d3ff283510ff3f4b1953d09fad35795140b1c5d64f313967934d5")
);
assert_eq!(
q0.y.to_bytes(),
h("dccb558863804a881d4fff3455716c836cef230e5209594ddd33d85c565b19b1")
);
let q1 = map_to_curve(&u1);
assert_eq!(
q1.x.to_bytes(),
h("51cce63c50d972a6e51c61334f0f4875c9ac1cd2d3238412f84e31da7d980ef5")
);
assert_eq!(
q1.y.to_bytes(),
h("b45d1a36d00ad90e5ec7840a60a4de411917fbe7c82c3949a6e699e5a1b66aac")
);
let p = hash_to_curve(b"", DST).unwrap();
assert_eq!(
p[1..33],
h("2c15230b26dbc6fc9a37051158c95b79656e17a1a920b11394ca91c44247d3e4")
);
assert_eq!(
p[33..],
h("8a7a74985cc5c776cdfe4b1f19884970453912e9d31528c060be9ab5c43e8415")
);
}
#[test]
fn the_exceptional_input_maps_onto_the_curve() {
let q = map_to_curve(&Fp::ZERO);
assert!(bool::from(q.is_on_curve()));
let c2 = P256::B.mul(&Fp::to_mont([30, 0, 0, 0]).invert());
assert_eq!(q.x, c2);
}
#[test]
fn expand_message_refuses_what_rfc_9380_forbids() {
assert!(expand_message_xmd(b"", &[0u8; 256], &mut [0u8; 32]).is_err());
assert!(expand_message_xmd(b"", b"d", &mut [0u8; 255 * 32 + 1]).is_err());
assert!(expand_message_xmd(b"", b"d", &mut []).is_err());
}
}