use core::fmt;
use crate::hash::Digest;
use crate::public_key::bigint::BigUint;
use crate::public_key::ec_edwards::{EdwardsMulTable, EdwardsPoint, TwistedEdwardsCurve};
use crate::public_key::io::{
decode_biguints, encode_biguints, pem_unwrap, pem_wrap, xml_unwrap, xml_wrap,
};
use crate::Csprng;
const EDDSA_PUBLIC_LABEL: &str = "CRYPTOGRAPHY EDDSA PUBLIC KEY";
const EDDSA_PRIVATE_LABEL: &str = "CRYPTOGRAPHY EDDSA PRIVATE KEY";
#[derive(Clone, Debug)]
pub struct EdDsaPublicKey {
curve: TwistedEdwardsCurve,
a_point: EdwardsPoint,
a_table: EdwardsMulTable,
}
#[derive(Clone)]
pub struct EdDsaPrivateKey {
curve: TwistedEdwardsCurve,
d: BigUint,
a_point: EdwardsPoint,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct EdDsaSignature {
r_point: EdwardsPoint,
s: BigUint,
}
pub struct EdDsa;
impl PartialEq for EdDsaPublicKey {
fn eq(&self, other: &Self) -> bool {
self.curve.same_curve(&other.curve) && self.a_point == other.a_point
}
}
impl Eq for EdDsaPublicKey {}
impl PartialEq for EdDsaPrivateKey {
fn eq(&self, other: &Self) -> bool {
self.curve.same_curve(&other.curve) && self.d == other.d
}
}
impl Eq for EdDsaPrivateKey {}
impl EdDsaPublicKey {
#[must_use]
pub fn curve(&self) -> &TwistedEdwardsCurve {
&self.curve
}
#[must_use]
pub fn public_point(&self) -> &EdwardsPoint {
&self.a_point
}
#[must_use]
pub fn to_wire_bytes(&self) -> Vec<u8> {
self.curve.encode_point(&self.a_point)
}
#[must_use]
pub fn from_wire_bytes(curve: TwistedEdwardsCurve, bytes: &[u8]) -> Option<Self> {
let a_point = curve.decode_point(bytes)?;
if !validate_public_point(&curve, &a_point) {
return None;
}
let a_table = curve.precompute_mul_table(&a_point);
Some(Self {
curve,
a_point,
a_table,
})
}
#[must_use]
pub fn verify_message<H: Digest>(&self, message: &[u8], signature: &EdDsaSignature) -> bool {
if signature.s.is_zero() || signature.s >= self.curve.n {
return false;
}
if signature.r_point.is_neutral()
|| !self.curve.is_on_curve(&signature.r_point)
|| !point_in_prime_subgroup(&self.curve, &signature.r_point)
{
return false;
}
let challenge =
challenge_scalar::<H>(&self.curve, &signature.r_point, &self.a_point, message);
let lhs = self.curve.scalar_mul_base(&signature.s);
let rhs = self.curve.add(
&signature.r_point,
&self.curve.scalar_mul_cached(&self.a_table, &challenge),
);
lhs == rhs
}
#[must_use]
pub fn verify_message_bytes<H: Digest>(&self, message: &[u8], signature: &[u8]) -> bool {
let Some(signature) = EdDsaSignature::from_key_blob(signature, &self.curve) else {
return false;
};
self.verify_message::<H>(message, &signature)
}
#[must_use]
pub fn to_key_blob(&self) -> Vec<u8> {
encode_biguints(&[
&self.curve.p,
&self.curve.a,
&self.curve.d,
&self.curve.n,
&self.curve.gx,
&self.curve.gy,
&self.a_point.x,
&self.a_point.y,
])
}
#[must_use]
pub fn from_key_blob(blob: &[u8]) -> Option<Self> {
let mut fields = decode_biguints(blob)?.into_iter();
let p = fields.next()?;
let a = fields.next()?;
let d = fields.next()?;
let n = fields.next()?;
let gx = fields.next()?;
let gy = fields.next()?;
let ax = fields.next()?;
let ay = fields.next()?;
if fields.next().is_some() {
return None;
}
let curve = TwistedEdwardsCurve::new(p, a, d, n, gx, gy)?;
if ax >= curve.p || ay >= curve.p {
return None;
}
let a_point = EdwardsPoint::new(ax, ay);
if !validate_public_point(&curve, &a_point) {
return None;
}
let a_table = curve.precompute_mul_table(&a_point);
Some(Self {
curve,
a_point,
a_table,
})
}
#[must_use]
pub fn to_pem(&self) -> String {
pem_wrap(EDDSA_PUBLIC_LABEL, &self.to_key_blob())
}
#[must_use]
pub fn from_pem(pem: &str) -> Option<Self> {
let blob = pem_unwrap(EDDSA_PUBLIC_LABEL, pem)?;
Self::from_key_blob(&blob)
}
#[must_use]
pub fn to_xml(&self) -> String {
xml_wrap(
"EdDsaPublicKey",
&[
("p", &self.curve.p),
("a", &self.curve.a),
("d", &self.curve.d),
("n", &self.curve.n),
("gx", &self.curve.gx),
("gy", &self.curve.gy),
("ax", &self.a_point.x),
("ay", &self.a_point.y),
],
)
}
#[must_use]
pub fn from_xml(xml: &str) -> Option<Self> {
let mut fields = xml_unwrap(
"EdDsaPublicKey",
&["p", "a", "d", "n", "gx", "gy", "ax", "ay"],
xml,
)?
.into_iter();
let p = fields.next()?;
let a = fields.next()?;
let d = fields.next()?;
let n = fields.next()?;
let gx = fields.next()?;
let gy = fields.next()?;
let ax = fields.next()?;
let ay = fields.next()?;
if fields.next().is_some() {
return None;
}
let curve = TwistedEdwardsCurve::new(p, a, d, n, gx, gy)?;
if ax >= curve.p || ay >= curve.p {
return None;
}
let a_point = EdwardsPoint::new(ax, ay);
if !validate_public_point(&curve, &a_point) {
return None;
}
let a_table = curve.precompute_mul_table(&a_point);
Some(Self {
curve,
a_point,
a_table,
})
}
}
impl EdDsaPrivateKey {
#[must_use]
pub fn curve(&self) -> &TwistedEdwardsCurve {
&self.curve
}
#[must_use]
pub fn private_scalar(&self) -> &BigUint {
&self.d
}
#[must_use]
pub fn public_point(&self) -> &EdwardsPoint {
&self.a_point
}
#[must_use]
pub fn to_public_key(&self) -> EdDsaPublicKey {
EdDsaPublicKey {
curve: self.curve.clone(),
a_point: self.a_point.clone(),
a_table: self.curve.precompute_mul_table(&self.a_point),
}
}
#[must_use]
pub fn sign_message_with_nonce<H: Digest>(
&self,
message: &[u8],
nonce: &BigUint,
) -> Option<EdDsaSignature> {
if nonce.is_zero() || nonce >= &self.curve.n {
return None;
}
let r_point = self.curve.scalar_mul_base(nonce);
if r_point.is_neutral() {
return None;
}
let challenge = challenge_scalar::<H>(&self.curve, &r_point, &self.a_point, message);
let ed = BigUint::mod_mul(&challenge, &self.d, &self.curve.n);
let s = nonce.add_ref(&ed).modulo(&self.curve.n);
Some(EdDsaSignature { r_point, s })
}
#[must_use]
pub fn sign_message<H: Digest, R: Csprng>(
&self,
message: &[u8],
rng: &mut R,
) -> Option<EdDsaSignature> {
loop {
let nonce = self.curve.random_scalar(rng);
if let Some(signature) = self.sign_message_with_nonce::<H>(message, &nonce) {
return Some(signature);
}
}
}
#[must_use]
pub fn sign_message_bytes<H: Digest, R: Csprng>(
&self,
message: &[u8],
rng: &mut R,
) -> Option<Vec<u8>> {
let signature = self.sign_message::<H, R>(message, rng)?;
Some(signature.to_key_blob())
}
#[must_use]
pub fn to_key_blob(&self) -> Vec<u8> {
encode_biguints(&[
&self.curve.p,
&self.curve.a,
&self.curve.d,
&self.curve.n,
&self.curve.gx,
&self.curve.gy,
&self.d,
])
}
#[must_use]
pub fn from_key_blob(blob: &[u8]) -> Option<Self> {
let mut fields = decode_biguints(blob)?.into_iter();
let p = fields.next()?;
let a = fields.next()?;
let d_curve = fields.next()?;
let n = fields.next()?;
let gx = fields.next()?;
let gy = fields.next()?;
let d = fields.next()?;
if fields.next().is_some() {
return None;
}
let curve = TwistedEdwardsCurve::new(p, a, d_curve, n, gx, gy)?;
if d.is_zero() || d >= curve.n {
return None;
}
let a_point = curve.scalar_mul_base(&d);
Some(Self { curve, d, a_point })
}
#[must_use]
pub fn to_pem(&self) -> String {
pem_wrap(EDDSA_PRIVATE_LABEL, &self.to_key_blob())
}
#[must_use]
pub fn from_pem(pem: &str) -> Option<Self> {
let blob = pem_unwrap(EDDSA_PRIVATE_LABEL, pem)?;
Self::from_key_blob(&blob)
}
#[must_use]
pub fn to_xml(&self) -> String {
xml_wrap(
"EdDsaPrivateKey",
&[
("p", &self.curve.p),
("a", &self.curve.a),
("d", &self.curve.d),
("n", &self.curve.n),
("gx", &self.curve.gx),
("gy", &self.curve.gy),
("private", &self.d),
],
)
}
#[must_use]
pub fn from_xml(xml: &str) -> Option<Self> {
let mut fields = xml_unwrap(
"EdDsaPrivateKey",
&["p", "a", "d", "n", "gx", "gy", "private"],
xml,
)?
.into_iter();
let p = fields.next()?;
let a = fields.next()?;
let d_curve = fields.next()?;
let n = fields.next()?;
let gx = fields.next()?;
let gy = fields.next()?;
let d = fields.next()?;
if fields.next().is_some() {
return None;
}
let curve = TwistedEdwardsCurve::new(p, a, d_curve, n, gx, gy)?;
if d.is_zero() || d >= curve.n {
return None;
}
let a_point = curve.scalar_mul_base(&d);
Some(Self { curve, d, a_point })
}
}
impl fmt::Debug for EdDsaPrivateKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("EdDsaPrivateKey(<redacted>)")
}
}
impl EdDsaSignature {
#[must_use]
pub fn nonce_point(&self) -> &EdwardsPoint {
&self.r_point
}
#[must_use]
pub fn response(&self) -> &BigUint {
&self.s
}
#[must_use]
pub fn to_key_blob(&self) -> Vec<u8> {
encode_biguints(&[&self.r_point.x, &self.r_point.y, &self.s])
}
#[must_use]
pub fn from_key_blob(blob: &[u8], curve: &TwistedEdwardsCurve) -> Option<Self> {
let mut fields = decode_biguints(blob)?.into_iter();
let rx = fields.next()?;
let ry = fields.next()?;
let s = fields.next()?;
if fields.next().is_some() || s.is_zero() || s >= curve.n {
return None;
}
if rx >= curve.p || ry >= curve.p {
return None;
}
let r_point = EdwardsPoint::new(rx, ry);
if !curve.is_on_curve(&r_point) {
return None;
}
Some(Self { r_point, s })
}
}
impl EdDsa {
#[must_use]
pub fn generate<R: Csprng>(
curve: TwistedEdwardsCurve,
rng: &mut R,
) -> (EdDsaPublicKey, EdDsaPrivateKey) {
let (d, a_point) = curve.generate_keypair(rng);
let public = EdDsaPublicKey {
curve: curve.clone(),
a_point: a_point.clone(),
a_table: curve.precompute_mul_table(&a_point),
};
let private = EdDsaPrivateKey { curve, d, a_point };
(public, private)
}
#[must_use]
pub fn from_secret_scalar(
curve: TwistedEdwardsCurve,
secret: &BigUint,
) -> Option<(EdDsaPublicKey, EdDsaPrivateKey)> {
if secret.is_zero() || secret >= &curve.n {
return None;
}
let a_point = curve.scalar_mul_base(secret);
if !validate_public_point(&curve, &a_point) {
return None;
}
Some((
EdDsaPublicKey {
curve: curve.clone(),
a_point: a_point.clone(),
a_table: curve.precompute_mul_table(&a_point),
},
EdDsaPrivateKey {
curve,
d: secret.clone(),
a_point,
},
))
}
}
fn validate_public_point(curve: &TwistedEdwardsCurve, point: &EdwardsPoint) -> bool {
!point.is_neutral() && curve.is_on_curve(point) && point_in_prime_subgroup(curve, point)
}
fn point_in_prime_subgroup(curve: &TwistedEdwardsCurve, point: &EdwardsPoint) -> bool {
curve.scalar_mul(point, &curve.n).is_neutral()
}
fn challenge_scalar<H: Digest>(
curve: &TwistedEdwardsCurve,
r_point: &EdwardsPoint,
a_point: &EdwardsPoint,
message: &[u8],
) -> BigUint {
let mut transcript = curve.encode_point(r_point);
transcript.extend_from_slice(&curve.encode_point(a_point));
transcript.extend_from_slice(message);
BigUint::from_be_bytes(&H::digest(&transcript)).modulo(&curve.n)
}
#[cfg(test)]
mod tests {
use super::{EdDsa, EdDsaPrivateKey, EdDsaPublicKey, EdDsaSignature};
use crate::public_key::bigint::BigUint;
use crate::public_key::ec_edwards::ed25519;
use crate::public_key::io::encode_biguints;
use crate::{CtrDrbgAes256, Sha512};
fn rng() -> CtrDrbgAes256 {
CtrDrbgAes256::new(&[0x5a; 48])
}
#[test]
fn roundtrip_sign_verify_ed25519() {
let curve = ed25519();
let (public, private) = EdDsa::generate(curve, &mut rng());
let sig = private
.sign_message::<Sha512, _>(b"edwards signature", &mut rng())
.expect("sign");
assert!(public.verify_message::<Sha512>(b"edwards signature", &sig));
assert!(!public.verify_message::<Sha512>(b"wrong", &sig));
}
#[test]
fn sign_with_explicit_nonce_roundtrip() {
let curve = ed25519();
let secret = BigUint::from_u64(7);
let nonce = BigUint::from_u64(11);
let (public, private) = EdDsa::from_secret_scalar(curve, &secret).expect("explicit secret");
let sig = private
.sign_message_with_nonce::<Sha512>(b"abc", &nonce)
.expect("explicit nonce");
assert!(public.verify_message::<Sha512>(b"abc", &sig));
}
#[test]
fn sign_message_with_nonce_is_repeatable() {
let curve = ed25519();
let secret = BigUint::from_u64(7);
let nonce = BigUint::from_u64(11);
let (_public, private) =
EdDsa::from_secret_scalar(curve, &secret).expect("explicit secret");
let lhs = private
.sign_message_with_nonce::<Sha512>(b"abc", &nonce)
.expect("first signature");
let rhs = private
.sign_message_with_nonce::<Sha512>(b"abc", &nonce)
.expect("second signature");
assert_eq!(lhs, rhs);
}
#[test]
fn tampered_signature_is_rejected() {
let curve = ed25519();
let (public, private) = EdDsa::generate(curve, &mut rng());
let mut sig = private
.sign_message::<Sha512, _>(b"tamper", &mut rng())
.expect("sign");
sig.s = sig.s.add_ref(&BigUint::one()).modulo(&public.curve().n);
if sig.s.is_zero() {
sig.s = BigUint::one();
}
assert!(!public.verify_message::<Sha512>(b"tamper", &sig));
}
#[test]
fn key_serialization_roundtrip() {
let curve = ed25519();
let (public, private) = EdDsa::generate(curve, &mut rng());
let public_bin = public.to_key_blob();
let public_pem = public.to_pem();
let public_xml = public.to_xml();
assert_eq!(
EdDsaPublicKey::from_key_blob(&public_bin).expect("public binary"),
public
);
assert_eq!(
EdDsaPublicKey::from_pem(&public_pem).expect("public pem"),
public
);
assert_eq!(
EdDsaPublicKey::from_xml(&public_xml).expect("public xml"),
public
);
let private_bin = private.to_key_blob();
let private_pem = private.to_pem();
let private_xml = private.to_xml();
let private_round = EdDsaPrivateKey::from_key_blob(&private_bin).expect("private binary");
assert_eq!(private_round, private);
assert_eq!(private_round.to_public_key(), public);
assert_eq!(
EdDsaPrivateKey::from_pem(&private_pem).expect("private pem"),
private
);
assert_eq!(
EdDsaPrivateKey::from_xml(&private_xml).expect("private xml"),
private
);
}
#[test]
fn public_bytes_roundtrip() {
let curve = ed25519();
let (public, _) = EdDsa::generate(curve.clone(), &mut rng());
let bytes = public.to_wire_bytes();
let round = EdDsaPublicKey::from_wire_bytes(curve, &bytes).expect("public bytes");
assert_eq!(round, public);
}
#[test]
fn signature_binary_roundtrip() {
let curve = ed25519();
let (public, private) = EdDsa::generate(curve, &mut rng());
let sig = private
.sign_message::<Sha512, _>(b"serialize", &mut rng())
.expect("sign");
let blob = sig.to_key_blob();
let decoded = EdDsaSignature::from_key_blob(&blob, public.curve()).expect("decode sig");
assert_eq!(decoded, sig);
assert!(public.verify_message::<Sha512>(b"serialize", &decoded));
}
#[test]
fn signature_binary_rejects_out_of_range_s() {
let curve = ed25519();
let base = curve.base_point();
let blob = encode_biguints(&[&base.x, &base.y, &curve.n]);
assert!(EdDsaSignature::from_key_blob(&blob, &curve).is_none());
}
}