use core::fmt;
use crate::public_key::bigint::BigUint;
use crate::public_key::ec::{AffinePoint, CurveParams};
use crate::public_key::io::{
decode_biguints, encode_biguints, pem_unwrap, pem_wrap, xml_unwrap, xml_wrap,
};
use crate::Csprng;
const ECDH_PUBLIC_LABEL: &str = "CRYPTOGRAPHY ECDH PUBLIC KEY";
const ECDH_PRIVATE_LABEL: &str = "CRYPTOGRAPHY ECDH PRIVATE KEY";
#[derive(Clone, Debug)]
pub struct EcdhPublicKey {
curve: CurveParams,
q: AffinePoint,
}
#[derive(Clone)]
pub struct EcdhPrivateKey {
curve: CurveParams,
d: BigUint,
q: AffinePoint,
}
pub struct Ecdh;
impl EcdhPublicKey {
#[must_use]
pub fn curve(&self) -> &CurveParams {
&self.curve
}
#[must_use]
pub fn public_point(&self) -> &AffinePoint {
&self.q
}
#[must_use]
pub fn to_wire_bytes(&self) -> Vec<u8> {
self.curve.encode_point(&self.q)
}
#[must_use]
pub fn from_wire_bytes(curve: CurveParams, bytes: &[u8]) -> Option<Self> {
let q = curve.decode_point(bytes)?;
Some(Self { curve, q })
}
#[must_use]
pub fn to_key_blob(&self) -> Vec<u8> {
let h = BigUint::from_u64(self.curve.h);
let field_byte = u8::from(self.curve.gf2m_degree().is_some());
let mut out = vec![field_byte];
out.extend_from_slice(&encode_biguints(&[
&self.curve.p,
&self.curve.a,
&self.curve.b,
&self.curve.n,
&h,
&self.curve.gx,
&self.curve.gy,
&self.q.x,
&self.q.y,
]));
out
}
#[must_use]
pub fn from_key_blob(blob: &[u8]) -> Option<Self> {
let (&field_type, rest) = blob.split_first()?;
let mut fields = decode_biguints(rest)?.into_iter();
let field_prime = fields.next()?;
let curve_a = fields.next()?;
let curve_b = fields.next()?;
let subgroup_order = fields.next()?;
let cofactor_big = fields.next()?;
let base_x = fields.next()?;
let base_y = fields.next()?;
let public_x = fields.next()?;
let public_y = fields.next()?;
if fields.next().is_some() {
return None;
}
let cofactor = biguint_to_u64(&cofactor_big)?;
let curve = if field_type == 0x01 {
let field_degree = field_prime.bits().checked_sub(1)?;
CurveParams::new_binary(
field_prime,
field_degree,
curve_a,
curve_b,
subgroup_order,
cofactor,
(base_x, base_y),
)?
} else {
CurveParams::new(
field_prime,
curve_a,
curve_b,
subgroup_order,
cofactor,
base_x,
base_y,
)?
};
let public_point = AffinePoint::new(public_x, public_y);
if !curve.is_on_curve(&public_point) {
return None;
}
Some(Self {
curve,
q: public_point,
})
}
#[must_use]
pub fn to_pem(&self) -> String {
pem_wrap(ECDH_PUBLIC_LABEL, &self.to_key_blob())
}
#[must_use]
pub fn from_pem(pem: &str) -> Option<Self> {
Self::from_key_blob(&pem_unwrap(ECDH_PUBLIC_LABEL, pem)?)
}
#[must_use]
pub fn to_xml(&self) -> String {
let h = BigUint::from_u64(self.curve.h);
let degree = BigUint::from_u64(
u64::try_from(self.curve.gf2m_degree().unwrap_or(0)).expect("degree fits in u64"),
);
xml_wrap(
"EcdhPublicKey",
&[
("p", &self.curve.p),
("a", &self.curve.a),
("b", &self.curve.b),
("n", &self.curve.n),
("h", &h),
("degree", °ree),
("gx", &self.curve.gx),
("gy", &self.curve.gy),
("qx", &self.q.x),
("qy", &self.q.y),
],
)
}
#[must_use]
pub fn from_xml(xml: &str) -> Option<Self> {
let mut fields = xml_unwrap(
"EcdhPublicKey",
&["p", "a", "b", "n", "h", "degree", "gx", "gy", "qx", "qy"],
xml,
)?
.into_iter();
let field_prime = fields.next()?;
let curve_a = fields.next()?;
let curve_b = fields.next()?;
let subgroup_order = fields.next()?;
let cofactor_big = fields.next()?;
let degree_big = fields.next()?;
let base_x = fields.next()?;
let base_y = fields.next()?;
let public_x = fields.next()?;
let public_y = fields.next()?;
if fields.next().is_some() {
return None;
}
let cofactor = biguint_to_u64(&cofactor_big)?;
let field_degree = usize::try_from(biguint_to_u64(°ree_big)?).ok()?;
let curve = if field_degree > 0 {
CurveParams::new_binary(
field_prime,
field_degree,
curve_a,
curve_b,
subgroup_order,
cofactor,
(base_x, base_y),
)?
} else {
CurveParams::new(
field_prime,
curve_a,
curve_b,
subgroup_order,
cofactor,
base_x,
base_y,
)?
};
let public_point = AffinePoint::new(public_x, public_y);
if !curve.is_on_curve(&public_point) {
return None;
}
Some(Self {
curve,
q: public_point,
})
}
}
impl EcdhPrivateKey {
#[must_use]
pub fn curve(&self) -> &CurveParams {
&self.curve
}
#[must_use]
pub fn private_scalar(&self) -> &BigUint {
&self.d
}
#[must_use]
pub fn to_public_key(&self) -> EcdhPublicKey {
EcdhPublicKey {
curve: self.curve.clone(),
q: self.q.clone(),
}
}
#[must_use]
pub fn agree_x_coordinate(&self, peer: &EcdhPublicKey) -> Option<Vec<u8>> {
let s = self.curve.diffie_hellman(&self.d, &peer.q);
if s.is_infinity() {
return None;
}
let x_bytes = s.x.to_be_bytes();
let coord_len = self.curve.coord_len;
let mut out = vec![0u8; coord_len];
if x_bytes.len() <= coord_len {
out[coord_len - x_bytes.len()..].copy_from_slice(&x_bytes);
} else {
out.copy_from_slice(&x_bytes[x_bytes.len() - coord_len..]);
}
Some(out)
}
#[must_use]
pub fn to_key_blob(&self) -> Vec<u8> {
let h = BigUint::from_u64(self.curve.h);
let field_byte = u8::from(self.curve.gf2m_degree().is_some());
let mut out = vec![field_byte];
out.extend_from_slice(&encode_biguints(&[
&self.curve.p,
&self.curve.a,
&self.curve.b,
&self.curve.n,
&h,
&self.curve.gx,
&self.curve.gy,
&self.d,
]));
out
}
#[must_use]
pub fn from_key_blob(blob: &[u8]) -> Option<Self> {
let (&field_type, rest) = blob.split_first()?;
let mut fields = decode_biguints(rest)?.into_iter();
let field_prime = fields.next()?;
let curve_a = fields.next()?;
let curve_b = fields.next()?;
let subgroup_order = fields.next()?;
let cofactor_big = fields.next()?;
let base_x = fields.next()?;
let base_y = fields.next()?;
let private_scalar = fields.next()?;
if fields.next().is_some() {
return None;
}
let cofactor = biguint_to_u64(&cofactor_big)?;
let curve = if field_type == 0x01 {
let field_degree = field_prime.bits().checked_sub(1)?;
CurveParams::new_binary(
field_prime,
field_degree,
curve_a,
curve_b,
subgroup_order,
cofactor,
(base_x, base_y),
)?
} else {
CurveParams::new(
field_prime,
curve_a,
curve_b,
subgroup_order,
cofactor,
base_x,
base_y,
)?
};
if private_scalar.is_zero() || private_scalar.cmp(&curve.n).is_ge() {
return None;
}
let q = curve.scalar_mul(&curve.base_point(), &private_scalar);
Some(Self {
curve,
d: private_scalar,
q,
})
}
#[must_use]
pub fn to_pem(&self) -> String {
pem_wrap(ECDH_PRIVATE_LABEL, &self.to_key_blob())
}
#[must_use]
pub fn from_pem(pem: &str) -> Option<Self> {
Self::from_key_blob(&pem_unwrap(ECDH_PRIVATE_LABEL, pem)?)
}
#[must_use]
pub fn to_xml(&self) -> String {
let h = BigUint::from_u64(self.curve.h);
let degree = BigUint::from_u64(
u64::try_from(self.curve.gf2m_degree().unwrap_or(0)).expect("degree fits in u64"),
);
xml_wrap(
"EcdhPrivateKey",
&[
("p", &self.curve.p),
("a", &self.curve.a),
("b", &self.curve.b),
("n", &self.curve.n),
("h", &h),
("degree", °ree),
("gx", &self.curve.gx),
("gy", &self.curve.gy),
("d", &self.d),
],
)
}
#[must_use]
pub fn from_xml(xml: &str) -> Option<Self> {
let mut fields = xml_unwrap(
"EcdhPrivateKey",
&["p", "a", "b", "n", "h", "degree", "gx", "gy", "d"],
xml,
)?
.into_iter();
let field_prime = fields.next()?;
let curve_a = fields.next()?;
let curve_b = fields.next()?;
let subgroup_order = fields.next()?;
let cofactor_big = fields.next()?;
let degree_big = fields.next()?;
let base_x = fields.next()?;
let base_y = fields.next()?;
let private_scalar = fields.next()?;
if fields.next().is_some() {
return None;
}
let cofactor = biguint_to_u64(&cofactor_big)?;
let field_degree = usize::try_from(biguint_to_u64(°ree_big)?).ok()?;
let curve = if field_degree > 0 {
CurveParams::new_binary(
field_prime,
field_degree,
curve_a,
curve_b,
subgroup_order,
cofactor,
(base_x, base_y),
)?
} else {
CurveParams::new(
field_prime,
curve_a,
curve_b,
subgroup_order,
cofactor,
base_x,
base_y,
)?
};
if private_scalar.is_zero() || private_scalar.cmp(&curve.n).is_ge() {
return None;
}
let q = curve.scalar_mul(&curve.base_point(), &private_scalar);
Some(Self {
curve,
d: private_scalar,
q,
})
}
}
impl fmt::Debug for EcdhPrivateKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("EcdhPrivateKey(<redacted>)")
}
}
impl Ecdh {
#[must_use]
pub fn generate<R: Csprng>(curve: CurveParams, rng: &mut R) -> (EcdhPublicKey, EcdhPrivateKey) {
let (d, q) = curve.generate_keypair(rng);
(
EcdhPublicKey {
curve: curve.clone(),
q: q.clone(),
},
EcdhPrivateKey { curve, d, q },
)
}
}
fn biguint_to_u64(value: &BigUint) -> Option<u64> {
let bytes = value.to_be_bytes();
if bytes.len() > 8 {
return None;
}
let mut arr = [0u8; 8];
arr[8 - bytes.len()..].copy_from_slice(&bytes);
Some(u64::from_be_bytes(arr))
}
#[cfg(test)]
mod tests {
use super::{Ecdh, EcdhPrivateKey, EcdhPublicKey};
use crate::public_key::ec::{b163, p256, p384, p521, secp256k1};
use crate::CtrDrbgAes256;
fn rng() -> CtrDrbgAes256 {
CtrDrbgAes256::new(&[0x77; 48])
}
#[test]
fn agreement_p256() {
let mut rng = rng();
let (pub_a, priv_a) = Ecdh::generate(p256(), &mut rng);
let (pub_b, priv_b) = Ecdh::generate(p256(), &mut rng);
let shared_a = priv_a.agree_x_coordinate(&pub_b).expect("agree A");
let shared_b = priv_b.agree_x_coordinate(&pub_a).expect("agree B");
assert_eq!(shared_a, shared_b);
assert_eq!(shared_a.len(), 32); }
#[test]
fn agreement_p384() {
let mut rng = rng();
let (pub_a, priv_a) = Ecdh::generate(p384(), &mut rng);
let (pub_b, priv_b) = Ecdh::generate(p384(), &mut rng);
let shared_a = priv_a.agree_x_coordinate(&pub_b).expect("agree A");
let shared_b = priv_b.agree_x_coordinate(&pub_a).expect("agree B");
assert_eq!(shared_a, shared_b);
assert_eq!(shared_a.len(), 48); }
#[test]
fn agreement_secp256k1() {
let mut rng = rng();
let (pub_a, priv_a) = Ecdh::generate(secp256k1(), &mut rng);
let (pub_b, priv_b) = Ecdh::generate(secp256k1(), &mut rng);
let shared_a = priv_a.agree_x_coordinate(&pub_b).expect("agree A");
let shared_b = priv_b.agree_x_coordinate(&pub_a).expect("agree B");
assert_eq!(shared_a, shared_b);
}
#[test]
fn agreement_p521() {
let mut rng = rng();
let (pub_a, priv_a) = Ecdh::generate(p521(), &mut rng);
let (pub_b, priv_b) = Ecdh::generate(p521(), &mut rng);
let shared_a = priv_a.agree_x_coordinate(&pub_b).expect("agree A");
let shared_b = priv_b.agree_x_coordinate(&pub_a).expect("agree B");
assert_eq!(shared_a, shared_b);
assert_eq!(shared_a.len(), 66); }
#[test]
fn agreement_b163() {
let mut rng = rng();
let (pub_a, priv_a) = Ecdh::generate(b163(), &mut rng);
let (pub_b, priv_b) = Ecdh::generate(b163(), &mut rng);
let shared_a = priv_a.agree_x_coordinate(&pub_b).expect("agree A");
let shared_b = priv_b.agree_x_coordinate(&pub_a).expect("agree B");
assert_eq!(shared_a, shared_b);
}
#[test]
fn different_keys_give_different_secrets() {
let mut rng = rng();
let (_pub_a, priv_a) = Ecdh::generate(p256(), &mut rng);
let (pub_b, _) = Ecdh::generate(p256(), &mut rng);
let (pub_c, _) = Ecdh::generate(p256(), &mut rng);
let s1 = priv_a.agree_x_coordinate(&pub_b).expect("agree with B");
let s2 = priv_a.agree_x_coordinate(&pub_c).expect("agree with C");
assert_ne!(s1, s2);
}
#[test]
fn to_bytes_from_bytes_roundtrip() {
let mut rng = rng();
let (public, _) = Ecdh::generate(p256(), &mut rng);
let bytes = public.to_wire_bytes();
assert_eq!(bytes[0], 0x04); let recovered = EcdhPublicKey::from_wire_bytes(p256(), &bytes).expect("from_bytes");
assert_eq!(recovered.q, public.q);
}
#[test]
fn to_public_key_consistent() {
let mut rng = rng();
let (public, private) = Ecdh::generate(p256(), &mut rng);
let derived = private.to_public_key();
assert_eq!(derived.q, public.q);
}
#[test]
fn public_key_binary_roundtrip() {
let mut rng = rng();
let (public, _) = Ecdh::generate(p256(), &mut rng);
let blob = public.to_key_blob();
let recovered = EcdhPublicKey::from_key_blob(&blob).expect("from_binary");
assert_eq!(recovered.q, public.q);
}
#[test]
fn private_key_binary_roundtrip() {
let mut rng = rng();
let (_, private) = Ecdh::generate(p256(), &mut rng);
let blob = private.to_key_blob();
let recovered = EcdhPrivateKey::from_key_blob(&blob).expect("from_binary");
assert_eq!(recovered.d, private.d);
}
#[test]
fn public_key_pem_roundtrip() {
let mut rng = rng();
let (public, _) = Ecdh::generate(p384(), &mut rng);
let pem = public.to_pem();
assert!(pem.contains("CRYPTOGRAPHY ECDH PUBLIC KEY"));
let recovered = EcdhPublicKey::from_pem(&pem).expect("from_pem");
assert_eq!(recovered.q, public.q);
}
#[test]
fn private_key_pem_roundtrip() {
let mut rng = rng();
let (_, private) = Ecdh::generate(p384(), &mut rng);
let pem = private.to_pem();
assert!(pem.contains("CRYPTOGRAPHY ECDH PRIVATE KEY"));
let recovered = EcdhPrivateKey::from_pem(&pem).expect("from_pem");
assert_eq!(recovered.d, private.d);
}
#[test]
fn public_key_xml_roundtrip() {
let mut rng = rng();
let (public, _) = Ecdh::generate(secp256k1(), &mut rng);
let xml = public.to_xml();
assert!(xml.contains("EcdhPublicKey"));
let recovered = EcdhPublicKey::from_xml(&xml).expect("from_xml");
assert_eq!(recovered.q, public.q);
}
#[test]
fn private_key_xml_roundtrip() {
let mut rng = rng();
let (_, private) = Ecdh::generate(secp256k1(), &mut rng);
let xml = private.to_xml();
let recovered = EcdhPrivateKey::from_xml(&xml).expect("from_xml");
assert_eq!(recovered.d, private.d);
}
#[test]
fn debug_private_key_redacted() {
let mut rng = rng();
let (_, private) = Ecdh::generate(p256(), &mut rng);
assert_eq!(format!("{private:?}"), "EcdhPrivateKey(<redacted>)");
}
}