// Copyright (c) 2019-2026, Argenox Technologies LLC
// All rights reserved.
//
// SPDX-License-Identifier: GPL-2.0-only OR LicenseRef-Argenox-Commercial-License
use core::cmp::Ordering;
use crate::drbg::HmacDrbgSha256;
use crate::hash::{noxtls_sha256, noxtls_sha512};
use crate::internal_alloc::Vec;
use noxtls_core::{Error, Result};
use super::bignum::BigUint;
/// Prime-field ECC curves recognized by the noxtls compatibility layer.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum NamedCurve {
Secp192R1,
Secp224R1,
Secp256R1,
Secp384R1,
Secp521R1,
Secp192K1,
Secp224K1,
Secp256K1,
BrainpoolP256R1,
BrainpoolP384R1,
BrainpoolP512R1,
Curve25519,
Curve448,
}
/// Static compatibility metadata for a named ECC curve.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct NamedCurveInfo {
pub curve: NamedCurve,
pub mbedtls_name: &'static str,
pub tls_named_group: Option<u16>,
pub coordinate_len: usize,
pub internal_weierstrass_ops: bool,
}
/// Internal private scalar for generic prime-field short-Weierstrass curves.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct NamedEcPrivateKey {
curve: NamedCurve,
scalar: BigUint,
}
/// Internal public point for generic prime-field short-Weierstrass curves.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct NamedEcPublicKey {
curve: NamedCurve,
point: CurvePoint,
}
#[derive(Debug, Clone, Eq, PartialEq)]
struct CurvePoint {
x: BigUint,
y: BigUint,
infinity: bool,
}
#[derive(Debug, Clone, Eq, PartialEq)]
struct JacobianPoint {
x: BigUint,
y: BigUint,
z: BigUint,
infinity: bool,
}
struct CurveParams {
name: &'static str,
coordinate_len: usize,
p: &'static str,
a: &'static str,
b: &'static str,
n: &'static str,
gx: &'static str,
gy: &'static str,
}
pub const NOXTLS_MBEDTLS_ECC_CURVES: &[NamedCurveInfo] = &[
NamedCurveInfo {
curve: NamedCurve::Secp521R1,
mbedtls_name: "secp521r1",
tls_named_group: Some(0x0019),
coordinate_len: 66,
internal_weierstrass_ops: true,
},
NamedCurveInfo {
curve: NamedCurve::Secp384R1,
mbedtls_name: "secp384r1",
tls_named_group: Some(0x0018),
coordinate_len: 48,
internal_weierstrass_ops: true,
},
NamedCurveInfo {
curve: NamedCurve::Secp256R1,
mbedtls_name: "secp256r1",
tls_named_group: Some(0x0017),
coordinate_len: 32,
internal_weierstrass_ops: true,
},
NamedCurveInfo {
curve: NamedCurve::Secp224R1,
mbedtls_name: "secp224r1",
tls_named_group: Some(0x0015),
coordinate_len: 28,
internal_weierstrass_ops: true,
},
NamedCurveInfo {
curve: NamedCurve::Secp192R1,
mbedtls_name: "secp192r1",
tls_named_group: Some(0x0013),
coordinate_len: 24,
internal_weierstrass_ops: true,
},
NamedCurveInfo {
curve: NamedCurve::BrainpoolP512R1,
mbedtls_name: "brainpoolP512r1",
tls_named_group: Some(0x001C),
coordinate_len: 64,
internal_weierstrass_ops: true,
},
NamedCurveInfo {
curve: NamedCurve::BrainpoolP384R1,
mbedtls_name: "brainpoolP384r1",
tls_named_group: Some(0x001B),
coordinate_len: 48,
internal_weierstrass_ops: true,
},
NamedCurveInfo {
curve: NamedCurve::BrainpoolP256R1,
mbedtls_name: "brainpoolP256r1",
tls_named_group: Some(0x001A),
coordinate_len: 32,
internal_weierstrass_ops: true,
},
NamedCurveInfo {
curve: NamedCurve::Secp256K1,
mbedtls_name: "secp256k1",
tls_named_group: Some(0x0016),
coordinate_len: 32,
internal_weierstrass_ops: true,
},
NamedCurveInfo {
curve: NamedCurve::Secp224K1,
mbedtls_name: "secp224k1",
tls_named_group: None,
coordinate_len: 28,
internal_weierstrass_ops: true,
},
NamedCurveInfo {
curve: NamedCurve::Secp192K1,
mbedtls_name: "secp192k1",
tls_named_group: None,
coordinate_len: 24,
internal_weierstrass_ops: true,
},
NamedCurveInfo {
curve: NamedCurve::Curve25519,
mbedtls_name: "curve25519",
tls_named_group: Some(0x001D),
coordinate_len: 32,
internal_weierstrass_ops: false,
},
NamedCurveInfo {
curve: NamedCurve::Curve448,
mbedtls_name: "curve448",
tls_named_group: Some(0x001E),
coordinate_len: 56,
internal_weierstrass_ops: false,
},
];
impl NamedEcPrivateKey {
pub fn from_bytes(curve: NamedCurve, bytes: &[u8]) -> Result<Self> {
let params = params_for(curve)?;
if bytes.len() != params.coordinate_len {
return Err(Error::InvalidLength(
"named ec private scalar length mismatch",
));
}
let scalar = BigUint::from_be_bytes(bytes);
if scalar.is_zero() {
return Err(Error::CryptoFailure(
"named ec private scalar must be non-zero",
));
}
if scalar.cmp(&hex_bn(params.n)?) != Ordering::Less {
return Err(Error::CryptoFailure("named ec private scalar out of range"));
}
Ok(Self { curve, scalar })
}
pub fn curve(&self) -> NamedCurve {
self.curve
}
pub fn to_bytes(&self) -> Result<Vec<u8>> {
let params = params_for(self.curve)?;
self.scalar.to_be_bytes_padded(params.coordinate_len)
}
pub fn clear(&mut self) {
self.scalar.clear();
}
pub fn public_key(&self) -> Result<NamedEcPublicKey> {
let params = params_for(self.curve)?;
let point = scalar_mul(&self.scalar, &base_point(params), params)?;
if point.infinity {
return Err(Error::CryptoFailure(
"named ec public key derivation produced infinity",
));
}
Ok(NamedEcPublicKey {
curve: self.curve,
point,
})
}
pub fn diffie_hellman(&self, peer: &NamedEcPublicKey) -> Result<Vec<u8>> {
if self.curve != peer.curve {
return Err(Error::CryptoFailure("named ec ecdh curve mismatch"));
}
let params = params_for(self.curve)?;
peer.validate()?;
let shared = scalar_mul(&self.scalar, &peer.point, params)?;
if shared.infinity {
return Err(Error::CryptoFailure("named ec shared point is at infinity"));
}
let out = shared.x.to_be_bytes_padded(params.coordinate_len)?;
if is_all_zero(&out) {
return Err(Error::CryptoFailure("named ec shared secret is all-zero"));
}
Ok(out)
}
pub fn sign_digest(&self, digest: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {
let params = params_for(self.curve)?;
let n = hex_bn(params.n)?;
let g = base_point(params);
let e = BigUint::from_be_bytes(digest).modulo(&n);
for counter in 0_u32..64 {
let k = derive_signing_nonce(&self.scalar, digest, counter, params.coordinate_len)
.modulo(&n);
if k.is_zero() {
continue;
}
let rp = scalar_mul(&k, &g, params)?;
if rp.infinity {
continue;
}
let r = rp.x.modulo(&n);
if r.is_zero() {
continue;
}
let rd = mod_mul(&r, &self.scalar, &n);
let k_inv = mod_inv(&k, &n)?;
let s = mod_mul(&k_inv, &e.add(&rd).modulo(&n), &n);
if s.is_zero() {
continue;
}
return Ok((
r.to_be_bytes_padded(params.coordinate_len)?,
s.to_be_bytes_padded(params.coordinate_len)?,
));
}
Err(Error::CryptoFailure(
"named ec ecdsa nonce derivation exhausted",
))
}
}
impl Drop for NamedEcPrivateKey {
fn drop(&mut self) {
self.clear();
}
}
impl NamedEcPublicKey {
pub fn from_uncompressed(curve: NamedCurve, bytes: &[u8]) -> Result<Self> {
let params = params_for(curve)?;
let expected = 1 + (2 * params.coordinate_len);
if bytes.len() != expected {
return Err(Error::InvalidLength(
"named ec uncompressed point length mismatch",
));
}
if bytes[0] != 0x04 {
return Err(Error::ParseFailure(
"named ec point must be uncompressed SEC1 format",
));
}
let split = 1 + params.coordinate_len;
let key = Self {
curve,
point: CurvePoint {
x: BigUint::from_be_bytes(&bytes[1..split]),
y: BigUint::from_be_bytes(&bytes[split..]),
infinity: false,
},
};
key.validate()?;
Ok(key)
}
pub fn curve(&self) -> NamedCurve {
self.curve
}
pub fn to_uncompressed(&self) -> Result<Vec<u8>> {
let params = params_for(self.curve)?;
self.validate()?;
let mut out = Vec::with_capacity(1 + (2 * params.coordinate_len));
out.push(0x04);
out.extend_from_slice(&self.point.x.to_be_bytes_padded(params.coordinate_len)?);
out.extend_from_slice(&self.point.y.to_be_bytes_padded(params.coordinate_len)?);
Ok(out)
}
pub fn validate(&self) -> Result<()> {
let params = params_for(self.curve)?;
if self.point.infinity {
return Err(Error::CryptoFailure(
"named ec public point at infinity is invalid",
));
}
let p = hex_bn(params.p)?;
if self.point.x.cmp(&p) != Ordering::Less || self.point.y.cmp(&p) != Ordering::Less {
return Err(Error::CryptoFailure(
"named ec public point coordinates out of field range",
));
}
if !is_point_on_curve(&self.point, params)? {
return Err(Error::CryptoFailure(
"named ec public point is not on curve",
));
}
Ok(())
}
}
pub fn noxtls_named_curve_info(curve: NamedCurve) -> Option<NamedCurveInfo> {
NOXTLS_MBEDTLS_ECC_CURVES
.iter()
.copied()
.find(|info| info.curve == curve)
}
pub fn noxtls_named_curve_from_mbedtls_name(name: &str) -> Option<NamedCurve> {
NOXTLS_MBEDTLS_ECC_CURVES
.iter()
.find(|info| info.mbedtls_name.eq_ignore_ascii_case(name))
.map(|info| info.curve)
}
pub fn noxtls_named_ec_generate_private_key_auto(
curve: NamedCurve,
drbg: &mut HmacDrbgSha256,
) -> Result<NamedEcPrivateKey> {
let params = params_for(curve)?;
for _ in 0..64 {
let bytes = drbg.generate(params.coordinate_len, params.name.as_bytes())?;
if let Ok(key) = NamedEcPrivateKey::from_bytes(curve, &bytes) {
return Ok(key);
}
}
Err(Error::CryptoFailure(
"named ec private key generation exhausted retry budget",
))
}
pub fn noxtls_named_ecdh_shared_secret(
private_key: &NamedEcPrivateKey,
peer_public_key: &NamedEcPublicKey,
) -> Result<Vec<u8>> {
private_key.diffie_hellman(peer_public_key)
}
pub fn noxtls_named_ecdsa_sign_digest(
private_key: &NamedEcPrivateKey,
digest: &[u8],
) -> Result<(Vec<u8>, Vec<u8>)> {
private_key.sign_digest(digest)
}
pub fn noxtls_named_ecdsa_verify_digest(
public_key: &NamedEcPublicKey,
digest: &[u8],
r: &[u8],
s: &[u8],
) -> Result<()> {
public_key.validate()?;
let params = params_for(public_key.curve)?;
if r.len() != params.coordinate_len || s.len() != params.coordinate_len {
return Err(Error::InvalidLength(
"named ec ecdsa signature scalar length mismatch",
));
}
if is_all_zero(r) || is_all_zero(s) {
return Err(Error::CryptoFailure(
"named ec ecdsa signature scalars must be non-zero",
));
}
let n = hex_bn(params.n)?;
let r_bn = BigUint::from_be_bytes(r);
let s_bn = BigUint::from_be_bytes(s);
if r_bn.cmp(&n) != Ordering::Less || s_bn.cmp(&n) != Ordering::Less {
return Err(Error::CryptoFailure(
"named ec ecdsa signature scalars out of range",
));
}
let e = BigUint::from_be_bytes(digest).modulo(&n);
let w = mod_inv(&s_bn, &n)?;
let u1 = mod_mul(&e, &w, &n);
let u2 = mod_mul(&r_bn, &w, &n);
let p1 = scalar_mul_jacobian(&u1, &base_point(params), params);
let p2 = scalar_mul_jacobian(&u2, &public_key.point, params);
let r_point = jacobian_add(&p1, &p2, params).to_affine(params)?;
if r_point.infinity {
return Err(Error::CryptoFailure(
"named ec ecdsa verification produced point at infinity",
));
}
let v = r_point.x.modulo(&n);
if ct_bytes_eq(&v.to_be_bytes_padded(params.coordinate_len)?, r) {
return Ok(());
}
Err(Error::CryptoFailure("named ec ecdsa verification failed"))
}
pub fn noxtls_secp521r1_ecdsa_sign_sha512(
private_key: &NamedEcPrivateKey,
message: &[u8],
) -> Result<(Vec<u8>, Vec<u8>)> {
if private_key.curve != NamedCurve::Secp521R1 {
return Err(Error::CryptoFailure(
"secp521r1 signing requires a secp521r1 key",
));
}
private_key.sign_digest(&noxtls_sha512(message))
}
pub fn noxtls_secp256k1_ecdsa_sign_sha256(
private_key: &NamedEcPrivateKey,
message: &[u8],
) -> Result<(Vec<u8>, Vec<u8>)> {
if private_key.curve != NamedCurve::Secp256K1 {
return Err(Error::CryptoFailure(
"secp256k1 signing requires a secp256k1 key",
));
}
private_key.sign_digest(&noxtls_sha256(message))
}
fn params_for(curve: NamedCurve) -> Result<&'static CurveParams> {
match curve {
NamedCurve::Secp192R1 => Ok(&SECP192R1),
NamedCurve::Secp224R1 => Ok(&SECP224R1),
NamedCurve::Secp256R1 => Ok(&SECP256R1),
NamedCurve::Secp384R1 => Ok(&SECP384R1),
NamedCurve::Secp521R1 => Ok(&SECP521R1),
NamedCurve::Secp192K1 => Ok(&SECP192K1),
NamedCurve::Secp224K1 => Ok(&SECP224K1),
NamedCurve::Secp256K1 => Ok(&SECP256K1),
NamedCurve::BrainpoolP256R1 => Ok(&BRAINPOOLP256R1),
NamedCurve::BrainpoolP384R1 => Ok(&BRAINPOOLP384R1),
NamedCurve::BrainpoolP512R1 => Ok(&BRAINPOOLP512R1),
NamedCurve::Curve25519 | NamedCurve::Curve448 => Err(Error::UnsupportedFeature(
"montgomery named ec operations use x25519/x448 APIs",
)),
}
}
const SECP192R1: CurveParams = CurveParams {
name: "secp192r1",
coordinate_len: 24,
p: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFF",
a: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFC",
b: "64210519E59C80E70FA7E9AB72243049FEB8DEECC146B9B1",
n: "FFFFFFFFFFFFFFFFFFFFFFFF99DEF836146BC9B1B4D22831",
gx: "188DA80EB03090F67CBF20EB43A18800F4FF0AFD82FF1012",
gy: "07192B95FFC8DA78631011ED6B24CDD573F977A11E794811",
};
const SECP224R1: CurveParams = CurveParams {
name: "secp224r1",
coordinate_len: 28,
p: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000001",
a: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFE",
b: "B4050A850C04B3ABF54132565044B0B7D7BFD8BA270B39432355FFB4",
n: "FFFFFFFFFFFFFFFFFFFFFFFFFFFF16A2E0B8F03E13DD29455C5C2A3D",
gx: "B70E0CBD6BB4BF7F321390B94A03C1D356C21122343280D6115C1D21",
gy: "BD376388B5F723FB4C22DFE6CD4375A05A07476444D5819985007E34",
};
const SECP256R1: CurveParams = CurveParams {
name: "secp256r1",
coordinate_len: 32,
p: "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF",
a: "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC",
b: "5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B",
n: "FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551",
gx: "6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296",
gy: "4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5",
};
const SECP384R1: CurveParams = CurveParams { name: "secp384r1", coordinate_len: 48, p: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF", a: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC", b: "B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF", n: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973", gx: "AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7", gy: "3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F" };
const SECP521R1: CurveParams = CurveParams { name: "secp521r1", coordinate_len: 66, p: "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", a: "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC", b: "0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00", n: "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409", gx: "00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66", gy: "011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650" };
const SECP192K1: CurveParams = CurveParams {
name: "secp192k1",
coordinate_len: 24,
p: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFEE37",
a: "00",
b: "03",
n: "FFFFFFFFFFFFFFFFFFFFFFFE26F2FC170F69466A74DEFD8D",
gx: "DB4FF10EC057E9AE26B07D0280B7F4341DA5D1B1EAE06C7D",
gy: "9B2F2F6D9C5628A7844163D015BE86344082AA88D95E2F9D",
};
const SECP224K1: CurveParams = CurveParams {
name: "secp224k1",
coordinate_len: 28,
p: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFE56D",
a: "00",
b: "05",
n: "010000000000000000000000000001DCE8D2EC6184CAF0A971769FB1F7",
gx: "A1455B334DF099DF30FC28A169A467E9E47075A90F7E650EB6B7A45C",
gy: "7E089FED7FBA344282CAFBD6F7E319F7C0B0BD59E2CA4BDB556D61A5",
};
const SECP256K1: CurveParams = CurveParams {
name: "secp256k1",
coordinate_len: 32,
p: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F",
a: "00",
b: "07",
n: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
gx: "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
gy: "483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8",
};
const BRAINPOOLP256R1: CurveParams = CurveParams {
name: "brainpoolP256r1",
coordinate_len: 32,
p: "A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5377",
a: "7D5A0975FC2C3057EEF67530417AFFE7FB8055C126DC5C6CE94A4B44F330B5D9",
b: "26DC5C6CE94A4B44F330B5D9BBD77CBF958416295CF7E1CE6BCCDC18FF8C07B6",
n: "A9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7",
gx: "8BD2AEB9CB7E57CB2C4B482FFC81B7AFB9DE27E1E3BD23C23A4453BD9ACE3262",
gy: "547EF835C3DAC4FD97F8461A14611DC9C27745132DED8E545C1D54C72F046997",
};
const BRAINPOOLP384R1: CurveParams = CurveParams { name: "brainpoolP384r1", coordinate_len: 48, p: "8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB71123ACD3A729901D1A71874700133107EC53", a: "7BC382C63D8C150C3C72080ACE05AFA0C2BEA28E4FB22787139165EFBA91F90F8AA5814A503AD4EB04A8C7DD22CE2826", b: "04A8C7DD22CE28268B39B55416F0447C2FB77DE107DCD2A62E880EA53EEB62D57CB4390295DBC9943AB78696FA504C11", n: "8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B31F166E6CAC0425A7CF3AB6AF6B7FC3103B883202E9046565", gx: "1D1C64F068CF45FFA2A63A81B7C13F6B8847A3E77EF14FE3DB7FCAFE0CBD10E8E826E03436D646AAEF87B2E247D4AF1E", gy: "8ABE1D7520F9C2A45CB1EB8E95CFD55262B70B29FEEC5864E19C054FF99129280E4646217791811142820341263C5315" };
const BRAINPOOLP512R1: CurveParams = CurveParams { name: "brainpoolP512r1", coordinate_len: 64, p: "AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA703308717D4D9B009BC66842AECDA12AE6A380E62881FF2F2D82C68528AA6056583A48F3", a: "7830A3318B603B89E2327145AC234CC594CBDD8D3DF91610A83441CAEA9863BC2DED5D5AA8253AA10A2EF1C98B9AC8B57F1117A72BF2C7B9E7C1AC4D77FC94CA", b: "3DF91610A83441CAEA9863BC2DED5D5AA8253AA10A2EF1C98B9AC8B57F1117A72BF2C7B9E7C1AC4D77FC94CADC083E67984050B75EBAE5DD2809BD638016F723", n: "AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA70330870553E5C414CA92619418661197FAC10471DB1D381085DDADDB58796829CA90069", gx: "81AEE4BDD82ED9645A21322E9C4C6A9385ED9F70B5D916C1B43B62EEF4D0098EFF3B1F78E2D0D48D50D1687B93B97D5F7C6D5047406A5E688B352209BCB9F822", gy: "7DDE385D566332ECC0EABFA9CF7822FDF209F70024A57B1AA000C55B881F8111B2DCDE494A5F485E5BCA4BD88A2763AED1CA2B2FA8F0540678CD1E0F3AD80892" };
fn base_point(params: &CurveParams) -> CurvePoint {
CurvePoint {
x: hex_bn(params.gx).expect("static curve gx hex is valid"),
y: hex_bn(params.gy).expect("static curve gy hex is valid"),
infinity: false,
}
}
fn scalar_mul(scalar: &BigUint, point: &CurvePoint, params: &CurveParams) -> Result<CurvePoint> {
scalar_mul_jacobian(scalar, point, params).to_affine(params)
}
fn scalar_mul_jacobian(
scalar: &BigUint,
point: &CurvePoint,
params: &CurveParams,
) -> JacobianPoint {
if point.infinity {
return JacobianPoint::infinity();
}
let base = JacobianPoint::from_affine(point);
let table = precompute_nibble_window(&base, params);
let mut acc = JacobianPoint::infinity();
for byte in scalar.to_be_bytes() {
for nibble in [usize::from(byte >> 4), usize::from(byte & 0x0f)] {
for _ in 0..4 {
acc = jacobian_double(&acc, params);
}
if nibble != 0 {
acc = jacobian_add(&acc, &table[nibble], params);
}
}
}
acc
}
fn precompute_nibble_window(base: &JacobianPoint, params: &CurveParams) -> Vec<JacobianPoint> {
let mut table = Vec::with_capacity(16);
table.push(JacobianPoint::infinity());
table.push(base.clone());
for idx in 2..16 {
table.push(jacobian_add(&table[idx - 1], base, params));
}
table
}
fn is_point_on_curve(point: &CurvePoint, params: &CurveParams) -> Result<bool> {
if point.infinity {
return Ok(false);
}
let p = hex_bn(params.p)?;
let a = hex_bn(params.a)?;
let b = hex_bn(params.b)?;
let y_sq = mod_mul(&point.y, &point.y, &p);
let x_sq = mod_mul(&point.x, &point.x, &p);
let x_cu = mod_mul(&x_sq, &point.x, &p);
let ax = mod_mul(&a, &point.x, &p);
Ok(y_sq == mod_add(&mod_add(&x_cu, &ax, &p), &b, &p))
}
impl CurvePoint {
fn infinity() -> Self {
Self {
x: BigUint::zero(),
y: BigUint::zero(),
infinity: true,
}
}
}
impl JacobianPoint {
fn infinity() -> Self {
Self {
x: BigUint::zero(),
y: BigUint::zero(),
z: BigUint::zero(),
infinity: true,
}
}
fn from_affine(point: &CurvePoint) -> Self {
if point.infinity {
return Self::infinity();
}
Self {
x: point.x.clone(),
y: point.y.clone(),
z: BigUint::one(),
infinity: false,
}
}
fn to_affine(&self, params: &CurveParams) -> Result<CurvePoint> {
if self.infinity || self.z.is_zero() {
return Ok(CurvePoint::infinity());
}
let p = hex_bn(params.p)?;
let z_inv = mod_inv(&self.z, &p)?;
let z_inv2 = mod_mul(&z_inv, &z_inv, &p);
let z_inv3 = mod_mul(&z_inv2, &z_inv, &p);
Ok(CurvePoint {
x: mod_mul(&self.x, &z_inv2, &p),
y: mod_mul(&self.y, &z_inv3, &p),
infinity: false,
})
}
}
fn jacobian_double(a: &JacobianPoint, params: &CurveParams) -> JacobianPoint {
if a.infinity || a.y.is_zero() {
return JacobianPoint::infinity();
}
let p = hex_bn(params.p).expect("static curve p hex is valid");
let curve_a = hex_bn(params.a).expect("static curve a hex is valid");
let two = BigUint::from_u128(2);
let three = BigUint::from_u128(3);
let yy = mod_mul(&a.y, &a.y, &p);
let yyyy = mod_mul(&yy, &yy, &p);
let s = mod_mul(&BigUint::from_u128(4), &mod_mul(&a.x, &yy, &p), &p);
let z2 = mod_mul(&a.z, &a.z, &p);
let z4 = mod_mul(&z2, &z2, &p);
let m = mod_add(
&mod_mul(&three, &mod_mul(&a.x, &a.x, &p), &p),
&mod_mul(&curve_a, &z4, &p),
&p,
);
let x3 = mod_sub(&mod_mul(&m, &m, &p), &mod_mul(&two, &s, &p), &p);
let y3 = mod_sub(
&mod_mul(&m, &mod_sub(&s, &x3, &p), &p),
&mod_mul(&BigUint::from_u128(8), &yyyy, &p),
&p,
);
let z3 = mod_mul(&two, &mod_mul(&a.y, &a.z, &p), &p);
JacobianPoint {
x: x3,
y: y3,
z: z3,
infinity: false,
}
}
fn jacobian_add(a: &JacobianPoint, b: &JacobianPoint, params: &CurveParams) -> JacobianPoint {
if a.infinity {
return b.clone();
}
if b.infinity {
return a.clone();
}
let p = hex_bn(params.p).expect("static curve p hex is valid");
let two = BigUint::from_u128(2);
let z1z1 = mod_mul(&a.z, &a.z, &p);
let z2z2 = mod_mul(&b.z, &b.z, &p);
let u1 = mod_mul(&a.x, &z2z2, &p);
let u2 = mod_mul(&b.x, &z1z1, &p);
let s1 = mod_mul(&a.y, &mod_mul(&z2z2, &b.z, &p), &p);
let s2 = mod_mul(&b.y, &mod_mul(&z1z1, &a.z, &p), &p);
if u1 == u2 {
if s1 != s2 {
return JacobianPoint::infinity();
}
return jacobian_double(a, params);
}
let h = mod_sub(&u2, &u1, &p);
let i = mod_mul(&mod_mul(&two, &h, &p), &mod_mul(&two, &h, &p), &p);
let j = mod_mul(&h, &i, &p);
let r = mod_mul(&two, &mod_sub(&s2, &s1, &p), &p);
let v = mod_mul(&u1, &i, &p);
let x3 = mod_sub(
&mod_sub(&mod_mul(&r, &r, &p), &j, &p),
&mod_mul(&two, &v, &p),
&p,
);
let y3 = mod_sub(
&mod_mul(&r, &mod_sub(&v, &x3, &p), &p),
&mod_mul(&mod_mul(&two, &s1, &p), &j, &p),
&p,
);
let z_sum = mod_sub(
&mod_sub(
&mod_mul(&mod_add(&a.z, &b.z, &p), &mod_add(&a.z, &b.z, &p), &p),
&z1z1,
&p,
),
&z2z2,
&p,
);
let z3 = mod_mul(&z_sum, &h, &p);
JacobianPoint {
x: x3,
y: y3,
z: z3,
infinity: false,
}
}
fn mod_add(a: &BigUint, b: &BigUint, m: &BigUint) -> BigUint {
a.add(b).modulo(m)
}
fn mod_sub(a: &BigUint, b: &BigUint, m: &BigUint) -> BigUint {
if a.cmp(b) != Ordering::Less {
a.sub(b).modulo(m)
} else {
m.sub(&b.sub(a)).modulo(m)
}
}
fn mod_mul(a: &BigUint, b: &BigUint, m: &BigUint) -> BigUint {
a.mul(b).modulo(m)
}
fn mod_inv(a: &BigUint, m: &BigUint) -> Result<BigUint> {
BigUint::mod_inverse(a, m).ok_or(Error::CryptoFailure(
"named ec modular inverse is undefined",
))
}
fn derive_signing_nonce(
private_scalar: &BigUint,
digest: &[u8],
counter: u32,
len: usize,
) -> BigUint {
let mut seed = Vec::new();
seed.extend_from_slice(
&private_scalar
.to_be_bytes_padded(len)
.expect("private scalar fits"),
);
seed.extend_from_slice(digest);
seed.extend_from_slice(&counter.to_be_bytes());
if len > 48 {
BigUint::from_be_bytes(&noxtls_sha512(&seed))
} else {
BigUint::from_be_bytes(&noxtls_sha256(&seed))
}
}
fn hex_bn(hex: &str) -> Result<BigUint> {
Ok(BigUint::from_be_bytes(&hex_to_bytes(hex)?))
}
fn hex_to_bytes(hex: &str) -> Result<Vec<u8>> {
if (hex.len() & 1) != 0 {
return Err(Error::ParseFailure(
"curve parameter hex must have even length",
));
}
let mut out = Vec::with_capacity(hex.len() / 2);
let bytes = hex.as_bytes();
for idx in (0..bytes.len()).step_by(2) {
out.push((hex_nibble(bytes[idx])? << 4) | hex_nibble(bytes[idx + 1])?);
}
Ok(out)
}
fn hex_nibble(byte: u8) -> Result<u8> {
match byte {
b'0'..=b'9' => Ok(byte - b'0'),
b'a'..=b'f' => Ok(byte - b'a' + 10),
b'A'..=b'F' => Ok(byte - b'A' + 10),
_ => Err(Error::ParseFailure(
"curve parameter hex contains non-hex digit",
)),
}
}
fn is_all_zero(bytes: &[u8]) -> bool {
let mut acc = 0_u8;
for byte in bytes {
acc |= *byte;
}
acc == 0
}
fn ct_bytes_eq(left: &[u8], right: &[u8]) -> bool {
if left.len() != right.len() {
return false;
}
let mut diff = 0_u8;
for (&l, &r) in left.iter().zip(right.iter()) {
diff |= l ^ r;
}
diff == 0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mbedtls_curve_registry_includes_requested_families() {
assert_eq!(
noxtls_named_curve_from_mbedtls_name("secp521r1"),
Some(NamedCurve::Secp521R1)
);
assert_eq!(
noxtls_named_curve_from_mbedtls_name("secp256k1"),
Some(NamedCurve::Secp256K1)
);
assert_eq!(
noxtls_named_curve_from_mbedtls_name("brainpoolP512r1"),
Some(NamedCurve::BrainpoolP512R1)
);
assert!(NOXTLS_MBEDTLS_ECC_CURVES.len() >= 13);
}
#[test]
fn secp521r1_ecdh_matches_from_both_sides() {
let mut a_bytes = vec![0_u8; 66];
let mut b_bytes = vec![0_u8; 66];
a_bytes[65] = 1;
b_bytes[65] = 2;
let a = NamedEcPrivateKey::from_bytes(NamedCurve::Secp521R1, &a_bytes)
.expect("p521 scalar one");
let b = NamedEcPrivateKey::from_bytes(NamedCurve::Secp521R1, &b_bytes)
.expect("p521 scalar two");
let a_pub = a.public_key().expect("p521 public a");
let b_pub = b.public_key().expect("p521 public b");
assert_eq!(a_pub.to_uncompressed().expect("p521 point").len(), 133);
assert_eq!(
a.diffie_hellman(&b_pub).expect("p521 ecdh a"),
b.diffie_hellman(&a_pub).expect("p521 ecdh b")
);
}
#[test]
fn all_weierstrass_mbedtls_curves_derive_public_keys_and_ecdh() {
let curves = [
NamedCurve::Secp192R1,
NamedCurve::Secp224R1,
NamedCurve::Secp256R1,
NamedCurve::Secp384R1,
NamedCurve::Secp521R1,
NamedCurve::Secp192K1,
NamedCurve::Secp224K1,
NamedCurve::Secp256K1,
NamedCurve::BrainpoolP256R1,
NamedCurve::BrainpoolP384R1,
NamedCurve::BrainpoolP512R1,
];
for curve in curves {
let info = noxtls_named_curve_info(curve).expect("curve metadata");
assert!(info.internal_weierstrass_ops);
let mut one = vec![0_u8; info.coordinate_len];
let mut two = vec![0_u8; info.coordinate_len];
one[info.coordinate_len - 1] = 1;
two[info.coordinate_len - 1] = 2;
let private_one = NamedEcPrivateKey::from_bytes(curve, &one).expect("scalar one");
let private_two = NamedEcPrivateKey::from_bytes(curve, &two).expect("scalar two");
let public_one = private_one.public_key().expect("public one");
let public_two = private_two.public_key().expect("public two");
assert_eq!(
public_one.to_uncompressed().expect("sec1 public").len(),
1 + (2 * info.coordinate_len)
);
assert_eq!(
private_one.diffie_hellman(&public_two).expect("ecdh one"),
private_two.diffie_hellman(&public_one).expect("ecdh two")
);
}
}
#[test]
fn montgomery_curves_are_registry_only_for_named_weierstrass_api() {
assert!(
!noxtls_named_curve_info(NamedCurve::Curve25519)
.expect("curve25519")
.internal_weierstrass_ops
);
assert!(
!noxtls_named_curve_info(NamedCurve::Curve448)
.expect("curve448")
.internal_weierstrass_ops
);
assert!(NamedEcPrivateKey::from_bytes(NamedCurve::Curve25519, &[1_u8; 32]).is_err());
assert!(NamedEcPrivateKey::from_bytes(NamedCurve::Curve448, &[1_u8; 56]).is_err());
}
#[test]
fn secp256k1_ecdsa_sign_verify_roundtrip() {
let mut scalar = vec![0_u8; 32];
scalar[31] = 7;
let private =
NamedEcPrivateKey::from_bytes(NamedCurve::Secp256K1, &scalar).expect("k256 scalar");
let public = private.public_key().expect("k256 public");
let digest = noxtls_sha256(b"secp256k1 roundtrip");
let (r, s) = noxtls_named_ecdsa_sign_digest(&private, &digest).expect("k256 sign");
noxtls_named_ecdsa_verify_digest(&public, &digest, &r, &s).expect("k256 verify");
}
}