aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYawning Angel <yawning@schwanenlied.me>2026-02-03 18:21:50 +0900
committerYawning Angel <yawning@schwanenlied.me>2026-02-03 18:48:07 +0900
commiteadd5f789cf09e9a48f9120325fc22792ec1ff06 (patch)
treeaaf21d4e4ef8a9d46338d10ee3a8a9a418996a59
parent64ce2bdf0e84a52b4837f26062bb47643fab0bc5 (diff)
core/crypto/ecdh: Add secp384r1
-rw-r--r--core/crypto/ecdh/ecdh.odin71
-rw-r--r--tests/core/crypto/test_core_crypto_ecdh.odin13
2 files changed, 84 insertions, 0 deletions
diff --git a/core/crypto/ecdh/ecdh.odin b/core/crypto/ecdh/ecdh.odin
index 3e97ebdc7..80ef3f704 100644
--- a/core/crypto/ecdh/ecdh.odin
+++ b/core/crypto/ecdh/ecdh.odin
@@ -18,6 +18,7 @@ X448_Buf :: [x448.SCALAR_SIZE]byte
Curve :: enum {
Invalid,
SECP256R1,
+ SECP384R1,
X25519,
X448,
}
@@ -26,6 +27,7 @@ Curve :: enum {
CURVE_NAMES := [Curve]string {
.Invalid = "Invalid",
.SECP256R1 = "secp256r1",
+ .SECP384R1 = "secp384r1",
.X25519 = "X25519",
.X448 = "X448",
}
@@ -34,6 +36,7 @@ CURVE_NAMES := [Curve]string {
PRIVATE_KEY_SIZES := [Curve]int {
.Invalid = 0,
.SECP256R1 = secec.SC_SIZE_P256R1,
+ .SECP384R1 = secec.SC_SIZE_P384R1,
.X25519 = x25519.SCALAR_SIZE,
.X448 = x448.SCALAR_SIZE,
}
@@ -42,6 +45,7 @@ PRIVATE_KEY_SIZES := [Curve]int {
PUBLIC_KEY_SIZES := [Curve]int {
.Invalid = 0,
.SECP256R1 = 1 + 2 * secec.FE_SIZE_P256R1,
+ .SECP384R1 = 1 + 2 * secec.FE_SIZE_P384R1,
.X25519 = x25519.POINT_SIZE,
.X448 = x448.POINT_SIZE,
}
@@ -50,6 +54,7 @@ PUBLIC_KEY_SIZES := [Curve]int {
SHARED_SECRET_SIZES := [Curve]int {
.Invalid = 0,
.SECP256R1 = secec.FE_SIZE_P256R1,
+ .SECP384R1 = secec.FE_SIZE_P384R1,
.X25519 = x25519.POINT_SIZE,
.X448 = x448.POINT_SIZE,
}
@@ -58,6 +63,7 @@ SHARED_SECRET_SIZES := [Curve]int {
_PRIV_IMPL_IDS := [Curve]typeid {
.Invalid = nil,
.SECP256R1 = typeid_of(secec.Scalar_p256r1),
+ .SECP384R1 = typeid_of(secec.Scalar_p384r1),
.X25519 = typeid_of(X25519_Buf),
.X448 = typeid_of(X448_Buf),
}
@@ -66,6 +72,7 @@ _PRIV_IMPL_IDS := [Curve]typeid {
_PUB_IMPL_IDS := [Curve]typeid {
.Invalid = nil,
.SECP256R1 = typeid_of(secec.Point_p256r1),
+ .SECP384R1 = typeid_of(secec.Point_p384r1),
.X25519 = typeid_of(X25519_Buf),
.X448 = typeid_of(X448_Buf),
}
@@ -77,6 +84,7 @@ Private_Key :: struct {
_curve: Curve,
_impl: union {
secec.Scalar_p256r1,
+ secec.Scalar_p384r1,
X25519_Buf,
X448_Buf,
},
@@ -90,6 +98,7 @@ Public_Key :: struct {
_curve: Curve,
_impl: union {
secec.Point_p256r1,
+ secec.Point_p384r1,
X25519_Buf,
X448_Buf,
},
@@ -124,6 +133,18 @@ private_key_generate :: proc(priv_key: ^Private_Key, curve: Curve) -> bool {
break
}
}
+ case .SECP384R1:
+ sc := &priv_key._impl.(secec.Scalar_p384r1)
+
+ b: [48]byte = ---
+ defer (mem.zero_explicit(&b, size_of(b)))
+ for {
+ crypto.rand_bytes(b[:])
+ did_reduce := secec.sc_set_bytes(sc, b[:])
+ if !did_reduce && secec.sc_is_zero(sc) == 0 { // Likely
+ break
+ }
+ }
case .X25519:
sc := &priv_key._impl.(X25519_Buf)
crypto.rand_bytes(sc[:])
@@ -166,6 +187,17 @@ private_key_set_bytes :: proc(priv_key: ^Private_Key, curve: Curve, b: []byte) -
private_key_clear(priv_key)
return false
}
+ case .SECP384R1:
+ sc := &priv_key._impl.(secec.Scalar_p384r1)
+ did_reduce := secec.sc_set_bytes(sc, b)
+ is_zero := secec.sc_is_zero(sc) == 1
+
+ // Reject `0` and scalars that are not less than the
+ // curve order.
+ if did_reduce || is_zero {
+ private_key_clear(priv_key)
+ return false
+ }
case .X25519:
sc := &priv_key._impl.(X25519_Buf)
copy(sc[:], b)
@@ -190,6 +222,11 @@ private_key_generate_public :: proc(priv_key: ^Private_Key) {
secec.pt_scalar_mul_generator(&pub_key, &sc)
secec.pt_rescale(&pub_key, &pub_key)
priv_key._pub_key._impl = pub_key
+ case secec.Scalar_p384r1:
+ pub_key: secec.Point_p384r1 = ---
+ secec.pt_scalar_mul_generator(&pub_key, &sc)
+ secec.pt_rescale(&pub_key, &pub_key)
+ priv_key._pub_key._impl = pub_key
case X25519_Buf:
pub_key: X25519_Buf = ---
x25519.scalarmult_basepoint(pub_key[:], sc[:])
@@ -214,6 +251,9 @@ private_key_bytes :: proc(priv_key: ^Private_Key, dst: []byte) {
case .SECP256R1:
sc := &priv_key._impl.(secec.Scalar_p256r1)
secec.sc_bytes(dst, sc)
+ case .SECP384R1:
+ sc := &priv_key._impl.(secec.Scalar_p384r1)
+ secec.sc_bytes(dst, sc)
case .X25519:
sc := &priv_key._impl.(X25519_Buf)
copy(dst, sc[:])
@@ -236,6 +276,9 @@ private_key_equal :: proc(p, q: ^Private_Key) -> bool {
case .SECP256R1:
sc_p, sc_q := &p._impl.(secec.Scalar_p256r1), &q._impl.(secec.Scalar_p256r1)
return secec.sc_equal(sc_p, sc_q) == 1
+ case .SECP384R1:
+ sc_p, sc_q := &p._impl.(secec.Scalar_p384r1), &q._impl.(secec.Scalar_p384r1)
+ return secec.sc_equal(sc_p, sc_q) == 1
case .X25519:
b_p, b_q := &p._impl.(X25519_Buf), &q._impl.(X25519_Buf)
return crypto.compare_constant_time(b_p[:], b_q[:]) == 1
@@ -277,6 +320,16 @@ public_key_set_bytes :: proc(pub_key: ^Public_Key, curve: Curve, b: []byte) -> b
if !ok || secec.pt_is_identity(pt) == 1 {
return false
}
+ case .SECP384R1:
+ if b[0] != secec.SEC_PREFIX_UNCOMPRESSED {
+ return false
+ }
+
+ pt := &pub_key._impl.(secec.Point_p384r1)
+ ok := secec.pt_set_sec_bytes(pt, b)
+ if !ok || secec.pt_is_identity(pt) == 1 {
+ return false
+ }
case .X25519:
pt := &pub_key._impl.(X25519_Buf)
copy(pt[:], b)
@@ -313,6 +366,14 @@ public_key_bytes :: proc(pub_key: ^Public_Key, dst: []byte) {
dst[0] = secec.SEC_PREFIX_UNCOMPRESSED
secec.fe_bytes(dst[1:1+secec.FE_SIZE_P256R1], &pt.x)
secec.fe_bytes(dst[1+secec.FE_SIZE_P256R1:], &pt.y)
+ case .SECP384R1:
+ // Invariant: Unless the caller is manually building pub_key
+ // `Z = 1`, so we can skip the rescale.
+ pt := &pub_key._impl.(secec.Point_p384r1)
+
+ dst[0] = secec.SEC_PREFIX_UNCOMPRESSED
+ secec.fe_bytes(dst[1:1+secec.FE_SIZE_P384R1], &pt.x)
+ secec.fe_bytes(dst[1+secec.FE_SIZE_P384R1:], &pt.y)
case .X25519:
pt := &pub_key._impl.(X25519_Buf)
copy(dst, pt[:])
@@ -335,6 +396,9 @@ public_key_equal :: proc(p, q: ^Public_Key) -> bool {
case .SECP256R1:
pt_p, pt_q := &p._impl.(secec.Point_p256r1), &q._impl.(secec.Point_p256r1)
return secec.pt_equal(pt_p, pt_q) == 1
+ case .SECP384R1:
+ pt_p, pt_q := &p._impl.(secec.Point_p384r1), &q._impl.(secec.Point_p384r1)
+ return secec.pt_equal(pt_p, pt_q) == 1
case .X25519:
b_p, b_q := &p._impl.(X25519_Buf), &q._impl.(X25519_Buf)
return crypto.compare_constant_time(b_p[:], b_q[:]) == 1
@@ -369,6 +433,13 @@ ecdh :: proc(priv_key: ^Private_Key, pub_key: ^Public_Key, dst: []byte) -> bool
secec.pt_scalar_mul(&ss, pt, sc)
return secec.pt_bytes(dst, nil, &ss)
+ case .SECP384R1:
+ sc, pt := &priv_key._impl.(secec.Scalar_p384r1), &pub_key._impl.(secec.Point_p384r1)
+ ss: secec.Point_p384r1
+ defer secec.pt_clear(&ss)
+
+ secec.pt_scalar_mul(&ss, pt, sc)
+ return secec.pt_bytes(dst, nil, &ss)
case .X25519:
sc, pt := &priv_key._impl.(X25519_Buf), &pub_key._impl.(X25519_Buf)
x25519.scalarmult(dst, sc[:], pt[:])
diff --git a/tests/core/crypto/test_core_crypto_ecdh.odin b/tests/core/crypto/test_core_crypto_ecdh.odin
index 87c9e8a4d..e34671c84 100644
--- a/tests/core/crypto/test_core_crypto_ecdh.odin
+++ b/tests/core/crypto/test_core_crypto_ecdh.odin
@@ -52,6 +52,19 @@ test_ecdh :: proc(t: ^testing.T) {
"04809f04289c64348c01515eb03d5ce7ac1a8cb9498f5caa50197e58d43a86a7aeb29d84e811197f25eba8f5194092cb6ff440e26d4421011372461f579271cda3",
"057d636096cb80b67a8c038c890e887d1adfa4195e9b3ce241c8a778c59cda67",
},
+ // secp384r1 Test vectors (subset) from NIST CAVP
+ {
+ .SECP384R1,
+ "3cc3122a68f0d95027ad38c067916ba0eb8c38894d22e1b15618b6818a661774ad463b205da88cf699ab4d43c9cf98a1",
+ "04a7c76b970c3b5fe8b05d2838ae04ab47697b9eaf52e764592efda27fe7513272734466b400091adbf2d68c58e0c50066ac68f19f2e1cb879aed43a9969b91a0839c4c38a49749b661efedf243451915ed0905a32b060992b468c64766fc8437a",
+ "5f9d29dc5e31a163060356213669c8ce132e22f57c9a04f40ba7fcead493b457e5621e766c40a2e3d4d6a04b25e533f1",
+ },
+ {
+ .SECP384R1,
+ "92860c21bde06165f8e900c687f8ef0a05d14f290b3f07d8b3a8cc6404366e5d5119cd6d03fb12dc58e89f13df9cd783",
+ "0430f43fcf2b6b00de53f624f1543090681839717d53c7c955d1d69efaf0349b7363acb447240101cbb3af6641ce4b88e025e46c0c54f0162a77efcc27b6ea792002ae2ba82714299c860857a68153ab62e525ec0530d81b5aa15897981e858757",
+ "a23742a2c267d7425fda94b93f93bbcc24791ac51cd8fd501a238d40812f4cbfc59aac9520d758cf789c76300c69d2ff",
+ },
}
for v, _ in test_vectors {