[features]
default = ["validation"]
std = []
-validation = ["bitcoin_hashes", "ring", "hex_lit"]
+validation = ["bitcoin_hashes", "hex_lit"]
tokio = ["tokio_crate/net", "tokio_crate/io-util", "std"]
build_server = ["tokio", "tokio_crate/rt-multi-thread", "tokio_crate/macros"]
[dependencies]
-ring = { version = "0.17", default-features = false, features = ["alloc"], optional = true }
bitcoin_hashes = { version = "0.14", default-features = false, optional = true }
hex_lit = { version = "0.1", default-features = false, features = ["rust_v_1_46"], optional = true }
tokio_crate = { package = "tokio", version = "1.0", default-features = false, optional = true }
use criterion::{Criterion, criterion_group, criterion_main};
use dnssec_prover::crypto::rsa::validate_rsa;
+use dnssec_prover::crypto::secp256r1;
pub fn bench_validate_rsa(bench: &mut Criterion) {
// A signature by the root key over the root DNSKEY RRSet
}));
}
+pub fn bench_validate_secp256r1(bench: &mut Criterion) {
+ // A signature by the com. key over the com. DNSKEY RRSet
+ let pk = [183, 31, 4, 101, 16, 29, 219, 226, 191, 12, 148, 85, 209, 47, 161, 108, 28, 218, 68, 244, 191, 27, 162, 85, 52, 24, 173, 31, 58, 169, 176, 105, 115, 242, 27, 132, 235, 83, 44, 244, 3, 94, 232, 212, 131, 44, 162, 109, 137, 48, 106, 125, 50, 86, 12, 12, 176, 18, 157, 69, 10, 193, 8, 53];
+ let sig = [207, 89, 121, 239, 214, 5, 201, 157, 91, 15, 126, 57, 251, 60, 13, 82, 33, 137, 67, 212, 128, 161, 32, 93, 133, 247, 165, 154, 143, 126, 112, 177, 71, 23, 220, 48, 182, 191, 235, 38, 123, 7, 183, 244, 255, 239, 156, 194, 199, 48, 23, 100, 97, 240, 232, 81, 92, 31, 150, 66, 123, 249, 135, 224];
+ let rrset_hash = [148, 51, 14, 246, 192, 70, 100, 66, 31, 67, 171, 251, 100, 195, 224, 203, 145, 160, 238, 28, 26, 167, 115, 82, 17, 58, 83, 114, 97, 53, 182, 30];
+ bench.bench_function("validate_secp256r1", |b| b.iter(|| {
+ secp256r1::validate_ecdsa(&pk, &sig, &rrset_hash).unwrap();
+ }));
+}
+
criterion_group!(benches,
bench_validate_rsa,
+ bench_validate_secp256r1,
);
criterion_main!(benches);
--- /dev/null
+//! Simple verification of ECDSA signatures over SECP Random curves
+
+use super::bigint::*;
+
+pub(super) trait IntMod: Clone + Eq + Sized {
+ type I: Int;
+ fn from_i(v: Self::I) -> Self;
+ fn from_modinv_of(v: Self::I) -> Result<Self, ()>;
+
+ const ZERO: Self;
+ const ONE: Self;
+
+ fn mul(&self, o: &Self) -> Self;
+ fn square(&self) -> Self;
+ fn add(&self, o: &Self) -> Self;
+ fn sub(&self, o: &Self) -> Self;
+ fn double(&self) -> Self;
+ fn times_three(&self) -> Self;
+ fn times_four(&self) -> Self;
+ fn times_eight(&self) -> Self;
+
+ fn into_i(self) -> Self::I;
+}
+impl<M: PrimeModulus<U256> + Clone + Eq> IntMod for U256Mod<M> {
+ type I = U256;
+ fn from_i(v: Self::I) -> Self { U256Mod::from_u256(v) }
+ fn from_modinv_of(v: Self::I) -> Result<Self, ()> { U256Mod::from_modinv_of(v) }
+
+ const ZERO: Self = U256Mod::<M>::from_u256_panicking(U256::zero());
+ const ONE: Self = U256Mod::<M>::from_u256_panicking(U256::one());
+
+ fn mul(&self, o: &Self) -> Self { self.mul(o) }
+ fn square(&self) -> Self { self.square() }
+ fn add(&self, o: &Self) -> Self { self.add(o) }
+ fn sub(&self, o: &Self) -> Self { self.sub(o) }
+ fn double(&self) -> Self { self.double() }
+ fn times_three(&self) -> Self { self.times_three() }
+ fn times_four(&self) -> Self { self.times_four() }
+ fn times_eight(&self) -> Self { self.times_eight() }
+
+ fn into_i(self) -> Self::I { self.into_u256() }
+}
+impl<M: PrimeModulus<U384> + Clone + Eq> IntMod for U384Mod<M> {
+ type I = U384;
+ fn from_i(v: Self::I) -> Self { U384Mod::from_u384(v) }
+ fn from_modinv_of(v: Self::I) -> Result<Self, ()> { U384Mod::from_modinv_of(v) }
+
+ const ZERO: Self = U384Mod::<M>::from_u384_panicking(U384::zero());
+ const ONE: Self = U384Mod::<M>::from_u384_panicking(U384::one());
+
+ fn mul(&self, o: &Self) -> Self { self.mul(o) }
+ fn square(&self) -> Self { self.square() }
+ fn add(&self, o: &Self) -> Self { self.add(o) }
+ fn sub(&self, o: &Self) -> Self { self.sub(o) }
+ fn double(&self) -> Self { self.double() }
+ fn times_three(&self) -> Self { self.times_three() }
+ fn times_four(&self) -> Self { self.times_four() }
+ fn times_eight(&self) -> Self { self.times_eight() }
+
+ fn into_i(self) -> Self::I { self.into_u384() }
+}
+
+pub(super) trait Curve : Copy {
+ type Int: Int;
+
+ // With const generics, both IntModP and IntModN can be replaced with a single IntMod.
+ type IntModP: IntMod<I = Self::Int>;
+ type IntModN: IntMod<I = Self::Int>;
+
+ type P: PrimeModulus<Self::Int>;
+ type N: PrimeModulus<Self::Int>;
+
+ // Curve parameters y^2 = x^3 + ax + b
+ const A: Self::IntModP;
+ const B: Self::IntModP;
+
+ const G: Point<Self>;
+}
+
+#[derive(Clone, PartialEq, Eq)]
+pub(super) struct Point<C: Curve + ?Sized> {
+ x: C::IntModP,
+ y: C::IntModP,
+ z: C::IntModP,
+}
+
+impl<C: Curve + ?Sized> Point<C> {
+ fn on_curve(x: &C::IntModP, y: &C::IntModP) -> Result<(), ()> {
+ let x_2 = x.square();
+ let x_3 = x_2.mul(&x);
+ let v = x_3.add(&C::A.mul(&x)).add(&C::B);
+
+ let y_2 = y.square();
+ if y_2 != v {
+ Err(())
+ } else {
+ Ok(())
+ }
+ }
+
+ #[cfg(debug_assertions)]
+ fn on_curve_z(x: &C::IntModP, y: &C::IntModP, z: &C::IntModP) -> Result<(), ()> {
+ let m = C::IntModP::from_modinv_of(z.clone().into_i())?;
+ let m_2 = m.square();
+ let m_3 = m_2.mul(&m);
+ let x_norm = x.mul(&m_2);
+ let y_norm = y.mul(&m_3);
+ Self::on_curve(&x_norm, &y_norm)
+ }
+
+ #[cfg(test)]
+ fn normalize_x(&self) -> Result<C::IntModP, ()> {
+ let m = C::IntModP::from_modinv_of(self.z.clone().into_i())?;
+ Ok(self.x.mul(&m.square()))
+ }
+
+ fn from_xy(x: C::Int, y: C::Int) -> Result<Self, ()> {
+ let x = C::IntModP::from_i(x);
+ let y = C::IntModP::from_i(y);
+ Self::on_curve(&x, &y)?;
+ Ok(Point { x, y, z: C::IntModP::ONE })
+ }
+
+ pub(super) const fn from_xy_assuming_on_curve(x: C::IntModP, y: C::IntModP) -> Self {
+ Point { x, y, z: C::IntModP::ONE }
+ }
+
+ /// Checks that `expected_x` is equal to our X affine coordinate (without modular inversion).
+ fn eq_x(&self, expected_x: &C::IntModN) -> Result<(), ()> {
+ debug_assert!(expected_x.clone().into_i() < C::P::PRIME, "N is < P");
+
+ // If x is between N and P the below calculations will fail and we'll spuriously reject a
+ // signature and the wycheproof tests will fail. We should in theory accept such
+ // signatures, but the probability of this happening at random is roughly 1/2^128, i.e. we
+ // really don't need to handle it in practice. Thus, we only bother to do this in tests.
+ #[allow(unused_mut, unused_assignments)]
+ let mut slow_check = None;
+ #[cfg(test)] {
+ slow_check = Some(C::IntModN::from_i(self.normalize_x()?.into_i()) == *expected_x);
+ }
+
+ let e: C::IntModP = C::IntModP::from_i(expected_x.clone().into_i());
+ if self.z == C::IntModP::ZERO { return Err(()); }
+ let ezz = e.mul(&self.z).mul(&self.z);
+ if self.x == ezz { Ok(()) } else {
+ if slow_check == Some(true) { Ok(()) } else { Err(()) }
+ }
+ }
+
+ fn double(&self) -> Result<Self, ()> {
+ if self.y == C::IntModP::ZERO { return Err(()); }
+ if self.z == C::IntModP::ZERO { return Err(()); }
+
+ let s = self.x.times_four().mul(&self.y.square());
+ let z_2 = self.z.square();
+ let z_4 = z_2.square();
+ let y_2 = self.y.square();
+ let y_4 = y_2.square();
+ let x_2 = self.x.square();
+ let m = x_2.times_three().add(&C::A.mul(&z_4));
+ let x = m.square().sub(&s.double());
+ let y = m.mul(&s.sub(&x)).sub(&y_4.times_eight());
+ let z = self.y.double().mul(&self.z);
+
+ #[cfg(debug_assertions)] { assert!(Self::on_curve_z(&x, &y, &z).is_ok()); }
+ Ok(Point { x, y, z })
+ }
+
+ fn add(&self, o: &Self) -> Result<Self, ()> {
+ let o_z_2 = o.z.square();
+ let self_z_2 = self.z.square();
+
+ let u1 = self.x.mul(&o_z_2);
+ let u2 = o.x.mul(&self_z_2);
+ let s1 = self.y.mul(&o.z.mul(&o_z_2));
+ let s2 = o.y.mul(&self.z.mul(&self_z_2));
+ if u1 == u2 {
+ if s1 != s2 { /* PAI */ return Err(()); }
+ return self.double();
+ }
+ let h = u2.sub(&u1);
+ let h_2 = h.square();
+ let h_3 = h.mul(&h_2);
+ let r = s2.sub(&s1);
+ let x = r.square().sub(&h_3).sub(&u1.double().mul(&h_2));
+ let y = r.mul(&u1.mul(&h_2).sub(&x)).sub(&s1.mul(&h_3));
+ let z = h.mul(&self.z).mul(&o.z);
+
+ #[cfg(debug_assertions)] { assert!(Self::on_curve_z(&x, &y, &z).is_ok()); }
+ Ok(Point { x, y, z})
+ }
+}
+
+/// Calculates i * I + j * J
+#[allow(non_snake_case)]
+fn add_two_mul<C: Curve>(i: C::IntModN, I: &Point<C>, j: C::IntModN, J: &Point<C>) -> Result<Point<C>, ()> {
+ let i = i.into_i();
+ let j = j.into_i();
+
+ if i == C::Int::ZERO { /* Infinity */ return Err(()); }
+ if j == C::Int::ZERO { /* Infinity */ return Err(()); }
+
+ let mut res_opt: Result<Point<C>, ()> = Err(());
+ let i_limbs = i.limbs();
+ let j_limbs = j.limbs();
+ let mut skip_limb = 0;
+ let mut limbs_skip_iter = i_limbs.iter().zip(j_limbs.iter());
+ while limbs_skip_iter.next() == Some((&0, &0)) {
+ skip_limb += 1;
+ }
+ for (idx, (il, jl)) in i_limbs.iter().zip(j_limbs.iter()).skip(skip_limb).enumerate() {
+ let start_bit = if idx == 0 {
+ core::cmp::min(il.leading_zeros(), jl.leading_zeros())
+ } else { 0 };
+ for b in start_bit..64 {
+ let i_bit = (*il & (1 << (63 - b))) != 0;
+ let j_bit = (*jl & (1 << (63 - b))) != 0;
+ if let Ok(res) = res_opt.as_mut() {
+ *res = res.double()?;
+ }
+ if i_bit {
+ if let Ok(res) = res_opt.as_mut() {
+ // The wycheproof tests expect to see signatures pass even if we hit PAI on an
+ // intermediate result. While that's fine, I'm too lazy to go figure out if all
+ // our PAI definitions are right and the probability of this happening at
+ // random is, basically, the probability of guessing a private key anyway, so
+ // its not really worth actually handling outside of tests.
+ #[cfg(test)] {
+ res_opt = res.add(I);
+ }
+ #[cfg(not(test))] {
+ *res = res.add(I)?;
+ }
+ } else {
+ res_opt = Ok(I.clone());
+ }
+ }
+ if j_bit {
+ if let Ok(res) = res_opt.as_mut() {
+ // The wycheproof tests expect to see signatures pass even if we hit PAI on an
+ // intermediate result. While that's fine, I'm too lazy to go figure out if all
+ // our PAI definitions are right and the probability of this happening at
+ // random is, basically, the probability of guessing a private key anyway, so
+ // its not really worth actually handling outside of tests.
+ #[cfg(test)] {
+ res_opt = res.add(J);
+ }
+ #[cfg(not(test))] {
+ *res = res.add(J)?;
+ }
+ } else {
+ res_opt = Ok(J.clone());
+ }
+ }
+ }
+ }
+ res_opt
+}
+
+/// Validates the given signature against the given public key and message digest.
+pub(super) fn validate_ecdsa<C: Curve>(pk: &[u8], sig: &[u8], hash_input: &[u8]) -> Result<(), ()> {
+ #![allow(non_snake_case)]
+
+ if pk.len() != C::Int::BYTES * 2 { return Err(()); }
+ if sig.len() != C::Int::BYTES * 2 { return Err(()); }
+
+ let (r_bytes, s_bytes) = sig.split_at(C::Int::BYTES);
+ let (pk_x_bytes, pk_y_bytes) = pk.split_at(C::Int::BYTES);
+
+ let pk_x = C::Int::from_be_bytes(pk_x_bytes)?;
+ let pk_y = C::Int::from_be_bytes(pk_y_bytes)?;
+ let PK = Point::from_xy(pk_x, pk_y)?;
+
+ // from_i and from_modinv_of both will simply mod if the value is out of range. While its
+ // perfectly safe to do so, the wycheproof tests expect such signatures to be rejected, so we
+ // do so here.
+ let r_u256 = C::Int::from_be_bytes(r_bytes)?;
+ if r_u256 > C::N::PRIME { return Err(()); }
+ let s_u256 = C::Int::from_be_bytes(s_bytes)?;
+ if s_u256 > C::N::PRIME { return Err(()); }
+
+ let r = C::IntModN::from_i(r_u256);
+ let s_inv = C::IntModN::from_modinv_of(s_u256)?;
+
+ let z = C::IntModN::from_i(C::Int::from_be_bytes(hash_input)?);
+
+ let u_a = z.mul(&s_inv);
+ let u_b = r.mul(&s_inv);
+
+ let V = add_two_mul(u_a, &C::G, u_b, &PK)?;
+ V.eq_x(&r)
+}
//! policy. Thus we go ahead and use that for our hashing needs.
pub mod bigint;
+mod ec;
pub mod hash;
pub mod rsa;
+pub mod secp256r1;
+pub mod secp384r1;
--- /dev/null
+//! secp256r1 validation for DNSSEC signatures
+
+use super::bigint::*;
+use super::ec;
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+struct P();
+impl PrimeModulus<U256> for P {
+ const PRIME: U256 = U256::from_32_be_bytes_panicking(&hex_lit::hex!(
+ "ffffffff00000001000000000000000000000000ffffffffffffffffffffffff"));
+ const R_SQUARED_MOD_PRIME: U256 = U256::from_32_be_bytes_panicking(&hex_lit::hex!(
+ "00000004fffffffdfffffffffffffffefffffffbffffffff0000000000000003"));
+ const NEGATIVE_PRIME_INV_MOD_R: U256 = U256::from_32_be_bytes_panicking(&hex_lit::hex!(
+ "ffffffff00000002000000000000000000000001000000000000000000000001"));
+}
+#[derive(Clone, Copy, PartialEq, Eq)]
+struct N();
+impl PrimeModulus<U256> for N {
+ const PRIME: U256 = U256::from_32_be_bytes_panicking(&hex_lit::hex!(
+ "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551"));
+ const R_SQUARED_MOD_PRIME: U256 = U256::from_32_be_bytes_panicking(&hex_lit::hex!(
+ "66e12d94f3d956202845b2392b6bec594699799c49bd6fa683244c95be79eea2"));
+ const NEGATIVE_PRIME_INV_MOD_R: U256 = U256::from_32_be_bytes_panicking(&hex_lit::hex!(
+ "60d06633a9d6281c50fe77ecc588c6f648c944087d74d2e4ccd1c8aaee00bc4f"));
+}
+
+#[derive(Clone, Copy)]
+struct P256();
+
+impl ec::Curve for P256 {
+ type Int = U256;
+ type IntModP = U256Mod<P>;
+ type IntModN = U256Mod<N>;
+
+ type P = P;
+ type N = N;
+
+ const A: U256Mod<P> = U256Mod::from_u256_panicking(U256::from_32_be_bytes_panicking(&hex_lit::hex!(
+ "ffffffff00000001000000000000000000000000fffffffffffffffffffffffc")));
+ const B: U256Mod<P> = U256Mod::from_u256_panicking(U256::from_32_be_bytes_panicking(&hex_lit::hex!(
+ "5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b")));
+
+ const G: ec::Point<P256> = ec::Point::from_xy_assuming_on_curve(
+ U256Mod::from_u256_panicking(U256::from_32_be_bytes_panicking(&hex_lit::hex!(
+ "6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296"))),
+ U256Mod::from_u256_panicking(U256::from_32_be_bytes_panicking(&hex_lit::hex!(
+ "4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5"))),
+ );
+}
+
+/// Validates the given signature against the given public key and message digest.
+pub fn validate_ecdsa(pk: &[u8], sig: &[u8], hash_input: &[u8]) -> Result<(), ()> {
+ ec::validate_ecdsa::<P256>(pk, sig, hash_input)
+}
--- /dev/null
+//! secp384r1 validation for DNSSEC signatures
+
+use super::bigint::*;
+use super::ec;
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+struct P();
+impl PrimeModulus<U384> for P {
+ const PRIME: U384 = U384::from_48_be_bytes_panicking(&hex_lit::hex!(
+ "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff"));
+ const R_SQUARED_MOD_PRIME: U384 = U384::from_48_be_bytes_panicking(&hex_lit::hex!(
+ "000000000000000000000000000000010000000200000000fffffffe000000000000000200000000fffffffe00000001"));
+ const NEGATIVE_PRIME_INV_MOD_R: U384 = U384::from_48_be_bytes_panicking(&hex_lit::hex!(
+ "00000014000000140000000c00000002fffffffcfffffffafffffffbfffffffe00000000000000010000000100000001"));
+}
+#[derive(Clone, Copy, PartialEq, Eq)]
+struct N();
+impl PrimeModulus<U384> for N {
+ const PRIME: U384 = U384::from_48_be_bytes_panicking(&hex_lit::hex!(
+ "ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973"));
+ const R_SQUARED_MOD_PRIME: U384 = U384::from_48_be_bytes_panicking(&hex_lit::hex!(
+ "0c84ee012b39bf213fb05b7a28266895d40d49174aab1cc5bc3e483afcb82947ff3d81e5df1aa4192d319b2419b409a9"));
+ const NEGATIVE_PRIME_INV_MOD_R: U384 = U384::from_48_be_bytes_panicking(&hex_lit::hex!(
+ "355ca87de39dbb1fa150206ce4f194ac78d4ba5866d61787ee6c8e3df45624ce54a885995d20bb2b6ed46089e88fdc45"));
+}
+
+#[derive(Clone, Copy)]
+struct P384();
+
+impl ec::Curve for P384 {
+ type Int = U384;
+ type IntModP = U384Mod<P>;
+ type IntModN = U384Mod<N>;
+
+ type P = P;
+ type N = N;
+
+ const A: U384Mod<P> = U384Mod::from_u384_panicking(U384::from_48_be_bytes_panicking(&hex_lit::hex!(
+ "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc")));
+ const B: U384Mod<P> = U384Mod::from_u384_panicking(U384::from_48_be_bytes_panicking(&hex_lit::hex!(
+ "b3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef")));
+
+ const G: ec::Point<P384> = ec::Point::from_xy_assuming_on_curve(
+ U384Mod::from_u384_panicking(U384::from_48_be_bytes_panicking(&hex_lit::hex!(
+ "aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7"))),
+ U384Mod::from_u384_panicking(U384::from_48_be_bytes_panicking(&hex_lit::hex!(
+ "3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f"))),
+ );
+}
+
+/// Validates the given signature against the given public key and message digest.
+pub fn validate_ecdsa(pk: &[u8], sig: &[u8], hash_input: &[u8]) -> Result<(), ()> {
+ ec::validate_ecdsa::<P384>(pk, sig, hash_input)
+}
use alloc::vec;
use core::cmp::{self, Ordering};
-use ring::signature;
-
use crate::base32;
use crate::crypto;
use crate::rr::*;
record.write_u16_len_prefixed_data(&mut signed_data);
}
+ hash_ctx.update(&signed_data);
+ let hash = hash_ctx.finish();
let sig_validation = match sig.alg {
- 8|10 => {
- hash_ctx.update(&signed_data);
- let hash = hash_ctx.finish();
- crypto::rsa::validate_rsa(&dnskey.pubkey, &sig.signature, hash.as_ref())
- .map_err(|_| ValidationError::Invalid)
- },
- 13|14 => {
- let alg = if sig.alg == 13 {
- &signature::ECDSA_P256_SHA256_FIXED
- } else {
- &signature::ECDSA_P384_SHA384_FIXED
- };
-
- // Add 0x4 identifier to the ECDSA pubkey as expected by ring.
- let mut key = Vec::with_capacity(dnskey.pubkey.len() + 1);
- key.push(0x4);
- key.extend_from_slice(&dnskey.pubkey);
-
- signature::UnparsedPublicKey::new(alg, &key)
- .verify(&signed_data, &sig.signature)
- .map_err(|_| ValidationError::Invalid)
- },
- 15 => {
- signature::UnparsedPublicKey::new(&signature::ED25519, &dnskey.pubkey)
- .verify(&signed_data, &sig.signature)
- .map_err(|_| ValidationError::Invalid)
- },
+ 8|10 => crypto::rsa::validate_rsa(&dnskey.pubkey, &sig.signature, hash.as_ref())
+ .map_err(|_| ValidationError::Invalid),
+ 13 => crypto::secp256r1::validate_ecdsa(&dnskey.pubkey, &sig.signature, hash.as_ref())
+ .map_err(|_| ValidationError::Invalid),
+ 14 => crypto::secp384r1::validate_ecdsa(&dnskey.pubkey, &sig.signature, hash.as_ref())
+ .map_err(|_| ValidationError::Invalid),
+ // TODO: 15 => ED25519
_ => return Err(ValidationError::UnsupportedAlgorithm),
};
#[cfg(fuzzing)] {