use crate::{
Basis, ByteOrder, Error,
curve::{Curve, Point},
domains,
field::{Arithmetic, Normal, Polynomial},
multiplication::{Hardware, Kernel, Portable},
validation::{self, Custom, Factor, Known, ValidationError, Validator},
words::Words,
};
use rand_core::TryCryptoRng;
use structured_pkix::algorithm::dstu4145::Reduction;
#[derive(Debug, Clone, Copy)]
pub struct Explicit<'a> {
pub degree: u32,
pub reduction: Reduction,
pub a: bool,
pub b: &'a [u8],
pub order: &'a [u8],
pub base_point: &'a [u8],
pub byte_order: ByteOrder,
}
#[derive(Debug, Clone)]
pub struct Domain {
inner: Inner,
}
#[derive(Debug, Clone)]
enum Inner {
Polynomial(Prepared<Polynomial>),
Normal(Prepared<Normal>),
HardwarePolynomial(Prepared<Polynomial<Hardware>>),
HardwareNormal(Prepared<Normal<Hardware>>),
}
trait Backend: Kernel {
fn polynomial(value: Prepared<Polynomial<Self>>) -> Inner;
fn normal(value: Prepared<Normal<Self>>) -> Inner;
}
impl Backend for Portable {
fn polynomial(value: Prepared<Polynomial<Self>>) -> Inner {
Inner::Polynomial(value)
}
fn normal(value: Prepared<Normal<Self>>) -> Inner {
Inner::Normal(value)
}
}
impl Backend for Hardware {
fn polynomial(value: Prepared<Polynomial<Self>>) -> Inner {
Inner::HardwarePolynomial(value)
}
fn normal(value: Prepared<Normal<Self>>) -> Inner {
Inner::HardwareNormal(value)
}
}
#[derive(Debug, Clone)]
struct Prepared<F> {
curve: Curve<F>,
base: Point,
order: Words,
}
#[derive(Debug)]
pub struct PublicKey {
domain: Domain,
point: Point,
}
impl Domain {
pub fn named(basis: Basis, degree: usize) -> Result<Self, Error> {
if let Some(kernel) = Hardware::detect() {
return named_using(basis, degree, kernel);
}
named_using(basis, degree, Portable)
}
pub fn explicit(input: Explicit<'_>) -> Result<Self, Error> {
prepare(input, &mut Known)
}
pub fn explicit_with_evidence<R: TryCryptoRng>(
input: Explicit<'_>,
factors: &[Factor<'_>],
random: &mut R,
) -> Result<Self, ValidationError<R::Error>> {
prepare(input, &mut Custom { factors, random })
}
#[must_use]
pub fn degree(&self) -> usize {
match &self.inner {
Inner::Polynomial(value) => value.curve.field.degree(),
Inner::Normal(value) => value.curve.field.degree(),
Inner::HardwarePolynomial(value) => value.curve.field.degree(),
Inner::HardwareNormal(value) => value.curve.field.degree(),
}
}
#[must_use]
pub fn order_bits(&self) -> usize {
match &self.inner {
Inner::Polynomial(value) => value.order.bits(),
Inner::Normal(value) => value.order.bits(),
Inner::HardwarePolynomial(value) => value.order.bits(),
Inner::HardwareNormal(value) => value.order.bits(),
}
}
pub fn public_key(self, bytes: &[u8], byte_order: ByteOrder) -> Result<PublicKey, Error> {
let compressed = element(bytes, self.degree(), byte_order)?;
let point = match &self.inner {
Inner::Polynomial(value) => value.public_key(compressed)?,
Inner::Normal(value) => value.public_key(compressed)?,
Inner::HardwarePolynomial(value) => value.public_key(compressed)?,
Inner::HardwareNormal(value) => value.public_key(compressed)?,
};
Ok(PublicKey {
domain: self,
point,
})
}
}
fn named_using<K: Backend>(basis: Basis, degree: usize, kernel: K) -> Result<Domain, Error> {
let spec = domains::named(basis, degree).ok_or(Error::UnsupportedDomain)?;
let inner = match basis {
Basis::Polynomial => {
let row = domains::POLYNOMIALS
.iter()
.find(|row| row[0] == degree)
.ok_or(Error::UnsupportedDomain)?;
let count = if row[2] == 0 { 1 } else { 3 };
K::polynomial(named(
Polynomial::new(degree, &row[1..=count], kernel)?,
spec,
)?)
}
Basis::OptimalNormal => K::normal(named(Normal::new(degree, kernel)?, spec)?),
};
Ok(Domain { inner })
}
impl PublicKey {
#[must_use]
pub fn degree(&self) -> usize {
self.domain.degree()
}
#[must_use]
pub fn order_bits(&self) -> usize {
self.domain.order_bits()
}
pub fn verify_prehash(
&self,
hash: &[u8],
signature: &[u8],
byte_order: ByteOrder,
) -> Result<bool, Error> {
if hash.is_empty() || hash.len() > 64 {
return Err(Error::InvalidEncoding);
}
let width = signature.len() / 2;
if !signature.len().is_multiple_of(2)
|| width < self.domain.order_bits().div_ceil(8)
|| width > 64
{
return Err(Error::InvalidEncoding);
}
let first = Words::read(&signature[..width], byte_order)?;
let last = Words::read(&signature[width..], byte_order)?;
let (r, s) = match byte_order {
ByteOrder::LittleEndian => (first, last),
ByteOrder::BigEndian => (last, first),
};
let mut digest = Words::read(hash, ByteOrder::LittleEndian)?;
digest.clear_above(self.domain.degree());
Ok(match &self.domain.inner {
Inner::Polynomial(value) => value.verify(self.point, digest, r, s),
Inner::Normal(value) => value.verify(self.point, digest, r, s),
Inner::HardwarePolynomial(value) => value.verify(self.point, digest, r, s),
Inner::HardwareNormal(value) => value.verify(self.point, digest, r, s),
})
}
}
fn named<F: Arithmetic>(field: F, spec: &domains::Spec) -> Result<Prepared<F>, Error> {
let base = Point {
x: field.import(spec.x),
y: field.import(spec.y),
};
let b = field.import(spec.b);
let curve = Curve::new(field, spec.a, b)?;
Ok(Prepared {
curve,
base,
order: spec.order,
})
}
fn prepare<V: Validator>(input: Explicit<'_>, validator: &mut V) -> Result<Domain, V::Error> {
if let Some(kernel) = Hardware::detect() {
return prepare_using(input, validator, kernel);
}
prepare_using(input, validator, Portable)
}
fn prepare_using<V: Validator, K: Backend>(
input: Explicit<'_>,
validator: &mut V,
kernel: K,
) -> Result<Domain, V::Error> {
let degree = usize::try_from(input.degree).map_err(|_| Error::UnsupportedDomain)?;
let inner = match input.reduction {
Reduction::Trinomial(exponent) => {
let exponent = usize::try_from(exponent).map_err(|_| Error::InvalidField)?;
let field = Polynomial::new(degree, &[exponent], kernel)?;
validator.polynomial(&field, &[exponent])?;
K::polynomial(explicit(field, input, validator)?)
}
Reduction::Pentanomial { k, j, l } => {
let terms = [
usize::try_from(k).map_err(|_| Error::InvalidField)?,
usize::try_from(j).map_err(|_| Error::InvalidField)?,
usize::try_from(l).map_err(|_| Error::InvalidField)?,
];
let field = Polynomial::new(degree, &terms, kernel)?;
validator.polynomial(&field, &terms)?;
K::polynomial(explicit(field, input, validator)?)
}
Reduction::OptimalNormal => {
K::normal(explicit(Normal::new(degree, kernel)?, input, validator)?)
}
};
Ok(Domain { inner })
}
fn explicit<F: Arithmetic, V: Validator>(
field: F,
input: Explicit<'_>,
validator: &mut V,
) -> Result<Prepared<F>, V::Error> {
let order = Words::read(input.order, ByteOrder::BigEndian)?;
validation::order_bounds(order, field.degree())?;
validator.order(order, field.degree())?;
let b = field.import(element(input.b, field.degree(), input.byte_order)?);
let compressed = element(input.base_point, field.degree(), input.byte_order)?;
let curve = Curve::new(field, input.a, b)?;
let base = curve
.decompress(compressed)
.map_err(|_| Error::InvalidDomain)?;
if base.x.is_zero() || curve.multiply(base, order).is_some() {
return Err(Error::InvalidDomain.into());
}
Ok(Prepared { curve, base, order })
}
fn element(bytes: &[u8], degree: usize, byte_order: ByteOrder) -> Result<Words, Error> {
if bytes.len() != degree.div_ceil(8) {
return Err(Error::InvalidEncoding);
}
let value = Words::read(bytes, byte_order)?;
if value.bits() > degree {
return Err(Error::InvalidEncoding);
}
Ok(value)
}
impl<F: Arithmetic> Prepared<F> {
fn public_key(&self, compressed: Words) -> Result<Point, Error> {
let point = self.curve.decompress(compressed)?;
if point.x.is_zero() || self.curve.multiply(point, self.order).is_some() {
return Err(Error::InvalidKey);
}
Ok(point)
}
fn verify(&self, public: Point, mut digest: Words, r: Words, s: Words) -> bool {
if r.is_zero() || s.is_zero() || !r.cmp(self.order).is_lt() || !s.cmp(self.order).is_lt() {
return false;
}
let field = &self.curve.field;
digest = field.import(digest);
if digest.is_zero() {
digest = field.one();
}
let left = self.curve.multiply(self.base, s);
let right = self.curve.multiply(public, r);
let Some(point) = self.curve.add(left, right) else {
return false;
};
let mut computed = field.export(field.multiply(digest, point.x));
computed.clear_above(self.order.bits() - 1);
computed == r
}
}