diff --git a/Cargo.lock b/Cargo.lock
index c618c8c34..0df25b3d9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -54,13 +54,19 @@ version = "0.14.0-pre"
 dependencies = [
  "belt-hash",
  "criterion",
+ "der",
+ "digest",
  "elliptic-curve",
  "hex",
  "hex-literal",
+ "hkdf",
+ "hmac",
+ "pkcs8",
  "primeorder",
  "proptest",
  "rand_core",
  "rfc6979",
+ "sec1",
  "signature",
 ]
 
diff --git a/bign256/Cargo.toml b/bign256/Cargo.toml
index b9761ee2c..54c1af92e 100644
--- a/bign256/Cargo.toml
+++ b/bign256/Cargo.toml
@@ -25,22 +25,32 @@ primeorder = { version = "=0.14.0-pre.0", optional = true, path = "../primeorder
 signature = { version = "=2.3.0-pre.3", optional = true }
 belt-hash = { version = "=0.2.0-pre.3", optional = true, default-features = false }
 rfc6979 = { version = "=0.5.0-pre.3", optional = true }
+rand_core = "0.6.4"
+pkcs8 = { version = "0.11.0-pre.0", optional = true }
+sec1 = { version = "0.8.0-pre.1", optional = true }
+der = { version = "0.8.0-pre.0" }
+
+digest = { version = "0.11.0-pre.8", optional = true }
+hkdf = { version = "0.13.0-pre.3", optional = true }
+hmac = { version = "0.13.0-pre.3", optional = true }
 
 [dev-dependencies]
 criterion = "0.5"
 hex-literal = "0.4"
 proptest = "1"
 rand_core = { version = "0.6", features = ["getrandom"] }
-hex = {version = "0.4" }
+hex = { version = "0.4" }
 
 [features]
-default = ["arithmetic", "pkcs8", "std", "dsa"]
+default = ["arithmetic", "pkcs8", "std", "ecdsa", "pem", "ecdh"]
 alloc = ["elliptic-curve/alloc", "primeorder?/alloc"]
 std = ["alloc", "elliptic-curve/std", "signature?/std"]
 
-dsa = ["arithmetic", "dep:rfc6979", "dep:signature", "dep:belt-hash"]
+ecdsa = ["arithmetic", "dep:rfc6979", "dep:signature", "dep:belt-hash"]
 arithmetic = ["dep:primeorder", "elliptic-curve/arithmetic"]
-pkcs8 = ["elliptic-curve/pkcs8"]
+pem = ["pkcs8", "sec1/pem"]
+pkcs8 = ["dep:pkcs8"]
+ecdh = ["arithmetic", "elliptic-curve/ecdh", "dep:digest", "dep:hkdf", "dep:hmac", "dep:belt-hash", "alloc"]
 
 [[bench]]
 name = "field"
diff --git a/bign256/src/ecdh.rs b/bign256/src/ecdh.rs
new file mode 100644
index 000000000..34b491e13
--- /dev/null
+++ b/bign256/src/ecdh.rs
@@ -0,0 +1,218 @@
+//! Elliptic Curve Diffie-Hellman Support.
+//!
+//! # ECDH Ephemeral (ECDHE) Usage
+//!
+//! Ephemeral Diffie-Hellman provides a one-time key exchange between two peers
+//! using a randomly generated set of keys for each exchange.
+//!
+//! In practice ECDHE is used as part of an [Authenticated Key Exchange (AKE)][AKE]
+//! protocol (e.g. [SIGMA]), where an existing cryptographic trust relationship
+//! can be used to determine the authenticity of the ephemeral keys, such as
+//! a digital signature. Without such an additional step, ECDHE is insecure!
+//! (see security warning below)
+//!
+//! See the documentation for the [`EphemeralSecret`] type for more information
+//! on performing ECDH ephemeral key exchanges.
+//!
+//! # Static ECDH Usage
+//!
+//! Static ECDH key exchanges are supported via the low-level
+//! [`diffie_hellman`] function.
+//!
+//! [AKE]: https://en.wikipedia.org/wiki/Authenticated_Key_Exchange
+//! [SIGMA]: https://webee.technion.ac.il/~hugo/sigma-pdf.pdf
+
+// use crate::{
+//     point::AffineCoordinates, AffinePoint, Curve, CurveArithmetic, FieldBytes, NonZeroScalar,
+//     ProjectivePoint, PublicKey,
+// };
+// use core::borrow::Borrow;
+// use digest::{crypto_common::BlockSizeUser, Digest};
+// use group::Curve as _;
+// use hkdf::{hmac::SimpleHmac, Hkdf};
+// use rand_core::CryptoRngCore;
+// use zeroize::{Zeroize, ZeroizeOnDrop};
+
+use crate::{AffinePoint, FieldBytes, NonZeroScalar, ProjectivePoint, PublicKey};
+use belt_hash::BeltHash;
+use core::borrow::Borrow;
+use elliptic_curve::point::AffineCoordinates;
+use elliptic_curve::zeroize::{Zeroize, ZeroizeOnDrop};
+use hkdf::Hkdf;
+use hmac::SimpleHmac;
+use rand_core::CryptoRngCore;
+
+/// Low-level Elliptic Curve Diffie-Hellman (ECDH) function.
+///
+/// Whenever possible, we recommend using the high-level ECDH ephemeral API
+/// provided by [`EphemeralSecret`].
+///
+/// However, if you are implementing a protocol which requires a static scalar
+/// value as part of an ECDH exchange, this API can be used to compute a
+/// [`SharedSecret`] from that value.
+///
+/// Note that this API operates on the low-level [`NonZeroScalar`] and
+/// [`AffinePoint`] types. If you are attempting to use the higher-level
+/// [`SecretKey`][`crate::SecretKey`] and [`PublicKey`] types, you will
+/// need to use the following conversions:
+///
+/// ```ignore
+/// let shared_secret = elliptic_curve::ecdh::diffie_hellman(
+///     secret_key.to_nonzero_scalar(),
+///     public_key.as_affine()
+/// );
+/// ```
+pub fn diffie_hellman(
+    secret_key: impl Borrow<NonZeroScalar>,
+    public_key: impl Borrow<AffinePoint>,
+) -> SharedSecret {
+    let public_point = ProjectivePoint::from(*public_key.borrow());
+    #[allow(clippy::arithmetic_side_effects)]
+    let secret_point = (public_point * secret_key.borrow().as_ref()).to_affine();
+    SharedSecret::new(secret_point)
+}
+
+/// Ephemeral Diffie-Hellman Secret.
+///
+/// These are ephemeral "secret key" values which are deliberately designed
+/// to avoid being persisted.
+///
+/// To perform an ephemeral Diffie-Hellman exchange, do the following:
+///
+/// - Have each participant generate an [`EphemeralSecret`] value
+/// - Compute the [`PublicKey`] for that value
+/// - Have each peer provide their [`PublicKey`] to their counterpart
+/// - Use [`EphemeralSecret`] and the other participant's [`PublicKey`]
+///   to compute a [`SharedSecret`] value.
+///
+/// # ⚠️ SECURITY WARNING ⚠️
+///
+/// Ephemeral Diffie-Hellman exchanges are unauthenticated and without a
+/// further authentication step are trivially vulnerable to man-in-the-middle
+/// attacks!
+///
+/// These exchanges should be performed in the context of a protocol which
+/// takes further steps to authenticate the peers in a key exchange.
+pub struct EphemeralSecret {
+    scalar: NonZeroScalar,
+}
+
+impl EphemeralSecret {
+    /// Generate a cryptographically random [`EphemeralSecret`].
+    pub fn random(rng: &mut impl CryptoRngCore) -> Self {
+        Self {
+            scalar: NonZeroScalar::random(rng),
+        }
+    }
+
+    /// Get the public key associated with this ephemeral secret.
+    ///
+    /// The `compress` flag enables point compression.
+    pub fn public_key(&self) -> PublicKey {
+        PublicKey::from_secret_scalar(&self.scalar)
+    }
+
+    /// Compute a Diffie-Hellman shared secret from an ephemeral secret and the
+    /// public key of the other participant in the exchange.
+    pub fn diffie_hellman(&self, public_key: &PublicKey) -> SharedSecret {
+        diffie_hellman(self.scalar, public_key.as_affine())
+    }
+}
+
+impl From<&EphemeralSecret> for PublicKey {
+    fn from(ephemeral_secret: &EphemeralSecret) -> Self {
+        ephemeral_secret.public_key()
+    }
+}
+
+impl Zeroize for EphemeralSecret {
+    fn zeroize(&mut self) {
+        self.scalar.zeroize()
+    }
+}
+
+impl ZeroizeOnDrop for EphemeralSecret {}
+
+impl Drop for EphemeralSecret {
+    fn drop(&mut self) {
+        self.zeroize();
+    }
+}
+
+/// Shared secret value computed via ECDH key agreement.
+pub struct SharedSecret {
+    /// Computed secret value
+    secret_bytes: FieldBytes,
+}
+
+impl SharedSecret {
+    /// Create a new [`SharedSecret`] from an [`AffinePoint`] for this curve.
+    #[inline]
+    fn new(point: AffinePoint) -> Self {
+        Self {
+            secret_bytes: point.x(),
+        }
+    }
+
+    /// Use [HKDF] (HMAC-based Extract-and-Expand Key Derivation Function) to
+    /// extract entropy from this shared secret.
+    ///
+    /// This method can be used to transform the shared secret into uniformly
+    /// random values which are suitable as key material.
+    ///
+    /// The `D` type parameter is a cryptographic digest function.
+    /// `sha2::Sha256` is a common choice for use with HKDF.
+    ///
+    /// The `salt` parameter can be used to supply additional randomness.
+    /// Some examples include:
+    ///
+    /// - randomly generated (but authenticated) string
+    /// - fixed application-specific value
+    /// - previous shared secret used for rekeying (as in TLS 1.3 and Noise)
+    ///
+    /// After initializing HKDF, use [`Hkdf::expand`] to obtain output key
+    /// material.
+    ///
+    /// [HKDF]: https://en.wikipedia.org/wiki/HKDF
+    pub fn extract(&self, salt: Option<&[u8]>) -> Hkdf<BeltHash, SimpleHmac<BeltHash>> {
+        Hkdf::new(salt, &self.secret_bytes)
+    }
+
+    /// This value contains the raw serialized x-coordinate of the elliptic curve
+    /// point computed from a Diffie-Hellman exchange, serialized as bytes.
+    ///
+    /// When in doubt, use [`SharedSecret::extract`] instead.
+    ///
+    /// # ⚠️ WARNING: NOT UNIFORMLY RANDOM! ⚠️
+    ///
+    /// This value is not uniformly random and should not be used directly
+    /// as a cryptographic key for anything which requires that property
+    /// (e.g. symmetric ciphers).
+    ///
+    /// Instead, the resulting value should be used as input to a Key Derivation
+    /// Function (KDF) or cryptographic hash function to produce a symmetric key.
+    /// The [`SharedSecret::extract`] function will do this for you.
+    pub fn raw_secret_bytes(&self) -> &FieldBytes {
+        &self.secret_bytes
+    }
+}
+
+impl From<FieldBytes> for SharedSecret {
+    /// NOTE: this impl is intended to be used by curve implementations to
+    /// instantiate a [`SharedSecret`] value from their respective
+    /// [`AffinePoint`] type.
+    ///
+    /// Curve implementations should provide the field element representing
+    /// the affine x-coordinate as `secret_bytes`.
+    fn from(secret_bytes: FieldBytes) -> Self {
+        Self { secret_bytes }
+    }
+}
+
+impl ZeroizeOnDrop for SharedSecret {}
+
+impl Drop for SharedSecret {
+    fn drop(&mut self) {
+        self.secret_bytes.zeroize()
+    }
+}
diff --git a/bign256/src/dsa.rs b/bign256/src/ecdsa.rs
similarity index 95%
rename from bign256/src/dsa.rs
rename to bign256/src/ecdsa.rs
index ffd084a7a..5197eaf0b 100644
--- a/bign256/src/dsa.rs
+++ b/bign256/src/ecdsa.rs
@@ -9,21 +9,21 @@
 //! # fn example() -> Result<(), Box<dyn std::error::Error>> {
 //! use rand_core::OsRng; // requires 'getrandom` feature
 //! use bign256::{
-//!     dsa::{Signature, SigningKey, signature::Signer},
+//!     ecdsa::{Signature, SigningKey, signature::Signer},
 //!     SecretKey
 //! };
 //!
 //! // Signing
 //! let secret_key = SecretKey::random(&mut OsRng); // serialize with `::to_bytes()`
 //! let signing_key = SigningKey::new(&secret_key)?;
-//! let verifying_key_bytes = signing_key.verifying_key().to_sec1_bytes();
+//! let verifying_key_bytes = signing_key.verifying_key().to_bytes();
 //! let message = b"test message";
 //! let signature: Signature = signing_key.sign(message);
 //!
 //! // Verifying
-//! use bign256::dsa::{VerifyingKey, signature::Verifier};
+//! use bign256::ecdsa::{VerifyingKey, signature::Verifier};
 //!
-//! let verifying_key = VerifyingKey::from_sec1_bytes(&verifying_key_bytes)?;
+//! let verifying_key = VerifyingKey::from_bytes(&verifying_key_bytes)?;
 //! verifying_key.verify(message, &signature)?;
 //! # Ok(())
 //! # }
diff --git a/bign256/src/dsa/signing.rs b/bign256/src/ecdsa/signing.rs
similarity index 100%
rename from bign256/src/dsa/signing.rs
rename to bign256/src/ecdsa/signing.rs
diff --git a/bign256/src/dsa/verifying.rs b/bign256/src/ecdsa/verifying.rs
similarity index 90%
rename from bign256/src/dsa/verifying.rs
rename to bign256/src/ecdsa/verifying.rs
index 64ce212f4..51cab3356 100644
--- a/bign256/src/dsa/verifying.rs
+++ b/bign256/src/ecdsa/verifying.rs
@@ -14,6 +14,9 @@
 //! 9. Return YES.
 //! ```
 
+#[cfg(feature = "alloc")]
+use alloc::boxed::Box;
+
 use super::{Signature, BELT_OID};
 use crate::{
     AffinePoint, BignP256, EncodedPoint, FieldBytes, Hash, ProjectivePoint, PublicKey, Scalar,
@@ -26,13 +29,11 @@ use elliptic_curve::{
     array::{consts::U32, typenum::Unsigned, Array},
     group::GroupEncoding,
     ops::{LinearCombination, Reduce},
-    sec1::ToEncodedPoint,
     Curve, Field, Group,
 };
 use signature::{hazmat::PrehashVerifier, Error, Result, Verifier};
 
-#[cfg(feature = "alloc")]
-use alloc::boxed::Box;
+use elliptic_curve::sec1::ToEncodedPoint;
 
 /// Bign256 public key used for verifying signatures are valid for a given
 /// message.
@@ -89,21 +90,16 @@ impl VerifyingKey {
         hasher.finalize_fixed()
     }
 
-    /// Initialize [`VerifyingKey`] from a SEC1-encoded public key.
-    pub fn from_sec1_bytes(bytes: &[u8]) -> Result<Self> {
-        let public_key = PublicKey::from_sec1_bytes(bytes).map_err(|_| Error::new())?;
+    /// Parse a [`VerifyingKey`] from a byte slice.
+    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
+        let public_key = PublicKey::from_bytes(bytes).map_err(|_| Error::new())?;
         Self::new(public_key)
     }
 
-    /// Convert this [`VerifyingKey`] into the
-    /// `Elliptic-Curve-Point-to-Octet-String` encoding described in
-    /// SEC 1: Elliptic Curve Cryptography (Version 2.0) section 2.3.3
-    /// (page 10).
-    ///
-    /// <http://www.secg.org/sec1-v2.pdf>
+    /// Serialize the [`VerifyingKey`] as a byte array.
     #[cfg(feature = "alloc")]
-    pub fn to_sec1_bytes(&self) -> Box<[u8]> {
-        self.public_key.to_sec1_bytes()
+    pub fn to_bytes(&self) -> Box<[u8]> {
+        self.public_key.to_bytes()
     }
 }
 
diff --git a/bign256/src/lib.rs b/bign256/src/lib.rs
index 2f6c58628..135b9f6b9 100644
--- a/bign256/src/lib.rs
+++ b/bign256/src/lib.rs
@@ -1,4 +1,3 @@
-// #![no_std]
 #![no_std]
 #![cfg_attr(docsrs, feature(doc_auto_cfg))]
 #![doc = include_str!("../README.md")]
@@ -30,32 +29,37 @@
 #[allow(unused_extern_crates)]
 extern crate alloc;
 
+pub use elliptic_curve::{self, bigint::U256};
+use elliptic_curve::{bigint::ArrayEncoding, consts::U32, Error, FieldBytesEncoding};
+
+#[cfg(feature = "arithmetic")]
+pub use arithmetic::{scalar::Scalar, AffinePoint, ProjectivePoint};
+
+/// Bign256 result type
+pub type Result<T> = core::result::Result<T, Error>;
+
 #[cfg(feature = "arithmetic")]
 pub mod arithmetic;
 
 #[cfg(any(feature = "test-vectors", test))]
 pub mod test_vectors;
 
-#[cfg(feature = "dsa")]
-pub mod dsa;
-
-pub use elliptic_curve::{self, bigint::U256};
-
+#[cfg(feature = "ecdh")]
+pub mod ecdh;
+#[cfg(feature = "ecdsa")]
+pub mod ecdsa;
 #[cfg(feature = "arithmetic")]
-pub use arithmetic::{scalar::Scalar, AffinePoint, ProjectivePoint};
+pub mod public_key;
+#[cfg(feature = "arithmetic")]
+pub mod secret_key;
 
 #[cfg(feature = "pkcs8")]
-pub use elliptic_curve::pkcs8;
-
-use elliptic_curve::{
-    array::Array,
-    bigint::ArrayEncoding,
-    consts::{U32, U33},
-    FieldBytesEncoding,
-};
+#[allow(dead_code)]
+const ALGORITHM_OID: pkcs8::ObjectIdentifier =
+    pkcs8::ObjectIdentifier::new_unwrap("1.2.112.0.2.0.34.101.45.2.1");
 
-#[cfg(feature = "dsa")]
-type Hash = belt_hash::digest::Output<belt_hash::BeltHash>;
+#[cfg(feature = "ecdsa")]
+type Hash = digest::Output<belt_hash::BeltHash>;
 
 /// Order of BIGN P-256's elliptic curve group (i.e. scalar modulus) in hexadecimal.
 const ORDER_HEX: &str = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD95C8ED60DFB4DFC7E5ABF99263D6607";
@@ -103,12 +107,9 @@ impl elliptic_curve::point::PointCompaction for BignP256 {
 #[cfg(feature = "pkcs8")]
 impl pkcs8::AssociatedOid for BignP256 {
     const OID: pkcs8::ObjectIdentifier =
-        pkcs8::ObjectIdentifier::new_unwrap("1.2.112.0.2.0.34.101.45.1");
+        pkcs8::ObjectIdentifier::new_unwrap("1.2.112.0.2.0.34.101.45.3.1");
 }
 
-/// Compressed SEC1-encoded BIGN P256 curve point.
-pub type CompressedPoint = Array<u8, U33>;
-
 /// BIGN P-256 field element serialized as bytes.
 ///
 /// Byte array containing a serialized field element value (base field or scalar).
@@ -132,14 +133,26 @@ impl FieldBytesEncoding<BignP256> for U256 {
 pub type NonZeroScalar = elliptic_curve::NonZeroScalar<BignP256>;
 
 /// BIGN P-256 public key.
+// #[cfg(feature = "arithmetic")]
+// pub type PublicKey = elliptic_curve::PublicKey<BignP256>;
+
+/// Generic scalar type with primitive functionality.#
 #[cfg(feature = "arithmetic")]
-pub type PublicKey = elliptic_curve::PublicKey<BignP256>;
+pub type ScalarPrimitive = elliptic_curve::ScalarPrimitive<BignP256>;
 
-/// BIGN P-256 secret key.
-pub type SecretKey = elliptic_curve::SecretKey<BignP256>;
+/// Elliptic curve BignP256 public key.
+#[cfg(feature = "arithmetic")]
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct PublicKey {
+    point: elliptic_curve::AffinePoint<BignP256>,
+}
 
-#[cfg(not(feature = "arithmetic"))]
-impl elliptic_curve::sec1::ValidatePublicKey for BignP256 {}
+/// Elliptic curve BignP256 Secret Key
+#[cfg(feature = "arithmetic")]
+#[derive(Copy, Clone, Debug)]
+pub struct SecretKey {
+    inner: ScalarPrimitive,
+}
 
 /// Bit representation of a BIGN P-256 scalar field element.
 #[cfg(feature = "bits")]
diff --git a/bign256/src/public_key.rs b/bign256/src/public_key.rs
new file mode 100644
index 000000000..c2209293d
--- /dev/null
+++ b/bign256/src/public_key.rs
@@ -0,0 +1,210 @@
+//! Public key types and traits
+
+#[cfg(feature = "alloc")]
+use alloc::{boxed::Box, fmt};
+use core::{fmt::Display, str::FromStr};
+
+use elliptic_curve::{
+    array::Array,
+    point::NonIdentity,
+    sec1::{FromEncodedPoint, ToEncodedPoint},
+    AffinePoint, CurveArithmetic, Error, Group,
+};
+use pkcs8::{
+    spki::{AlgorithmIdentifier, AssociatedAlgorithmIdentifier},
+    AssociatedOid, DecodePublicKey, EncodePublicKey, ObjectIdentifier,
+};
+
+use crate::{BignP256, EncodedPoint, NonZeroScalar, ProjectivePoint, PublicKey, ALGORITHM_OID};
+
+impl PublicKey {
+    /// Convert an [`AffinePoint`] into a [`PublicKey`]
+    pub fn from_affine(point: AffinePoint<BignP256>) -> Result<Self, Error> {
+        if ProjectivePoint::from(point).is_identity().into() {
+            Err(Error)
+        } else {
+            Ok(Self { point })
+        }
+    }
+
+    /// Compute a [`PublicKey`] from a secret [`NonZeroScalar`] value
+    /// (i.e. a secret key represented as a raw scalar value)
+    pub fn from_secret_scalar(scalar: &NonZeroScalar) -> Self {
+        // `NonZeroScalar` ensures the resulting point is not the identity
+        #[allow(clippy::arithmetic_side_effects)]
+        Self {
+            point: (<BignP256 as CurveArithmetic>::ProjectivePoint::generator() * scalar.as_ref())
+                .to_affine(),
+        }
+    }
+
+    /// Borrow the inner [`AffinePoint`] from this [`PublicKey`].
+    ///
+    /// In ECC, public keys are elliptic curve points.
+    pub fn as_affine(&self) -> &AffinePoint<BignP256> {
+        &self.point
+    }
+
+    /// Convert this [`PublicKey`] to a [`ProjectivePoint`] for the given curve
+    pub fn to_projective(&self) -> ProjectivePoint {
+        self.point.into()
+    }
+
+    /// Convert this [`PublicKey`] to a [`NonIdentity`] of the inner [`AffinePoint`]
+    pub fn to_nonidentity(&self) -> NonIdentity<AffinePoint<BignP256>> {
+        NonIdentity::new(self.point).unwrap()
+    }
+
+    /// Get [`PublicKey`] from bytes
+    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
+        let mut bytes = Array::clone_from_slice(bytes);
+        // It is because public_key in little endian
+        bytes[..32].reverse();
+        bytes[32..].reverse();
+
+        let point = EncodedPoint::from_untagged_bytes(&bytes);
+        let affine = AffinePoint::<BignP256>::from_encoded_point(&point);
+        if affine.is_none().into() {
+            Err(Error)
+        } else {
+            Ok(Self {
+                point: affine.unwrap(),
+            })
+        }
+    }
+
+    /// Get [`PublicKey`] from encoded point
+    pub fn from_encoded_point(point: EncodedPoint) -> Result<Self, Error> {
+        let affine = AffinePoint::<BignP256>::from_encoded_point(&point);
+        if affine.is_none().into() {
+            Err(Error)
+        } else {
+            Ok(Self {
+                point: affine.unwrap(),
+            })
+        }
+    }
+
+    #[cfg(feature = "alloc")]
+    /// Get bytes from [`PublicKey`]
+    pub fn to_bytes(&self) -> Box<[u8]> {
+        let mut bytes = self.point.to_encoded_point(false).to_bytes();
+        bytes[1..32 + 1].reverse();
+        bytes[33..].reverse();
+        bytes[1..].to_vec().into_boxed_slice()
+    }
+
+    #[cfg(feature = "alloc")]
+    /// Get encoded point from [`PublicKey`]
+    pub fn to_encoded_point(&self) -> EncodedPoint {
+        self.point.to_encoded_point(false)
+    }
+}
+
+impl AsRef<AffinePoint<BignP256>> for PublicKey {
+    fn as_ref(&self) -> &AffinePoint<BignP256> {
+        self.as_affine()
+    }
+}
+impl Copy for PublicKey {}
+impl From<NonIdentity<AffinePoint<BignP256>>> for PublicKey {
+    fn from(value: NonIdentity<AffinePoint<BignP256>>) -> Self {
+        Self::from(&value)
+    }
+}
+
+impl From<&NonIdentity<AffinePoint<BignP256>>> for PublicKey {
+    fn from(value: &NonIdentity<AffinePoint<BignP256>>) -> Self {
+        Self {
+            point: value.to_point(),
+        }
+    }
+}
+
+impl From<PublicKey> for NonIdentity<AffinePoint<BignP256>> {
+    fn from(value: PublicKey) -> Self {
+        Self::from(&value)
+    }
+}
+
+impl From<&PublicKey> for NonIdentity<AffinePoint<BignP256>> {
+    fn from(value: &PublicKey) -> Self {
+        PublicKey::to_nonidentity(value)
+    }
+}
+
+#[cfg(feature = "pkcs8")]
+impl AssociatedAlgorithmIdentifier for PublicKey {
+    type Params = ObjectIdentifier;
+
+    const ALGORITHM_IDENTIFIER: AlgorithmIdentifier<ObjectIdentifier> = AlgorithmIdentifier {
+        oid: ALGORITHM_OID,
+        parameters: Some(BignP256::OID),
+    };
+}
+
+#[cfg(feature = "pkcs8")]
+impl TryFrom<pkcs8::SubjectPublicKeyInfoRef<'_>> for PublicKey {
+    type Error = pkcs8::spki::Error;
+
+    fn try_from(spki: pkcs8::SubjectPublicKeyInfoRef<'_>) -> pkcs8::spki::Result<Self> {
+        Self::try_from(&spki)
+    }
+}
+
+#[cfg(feature = "pkcs8")]
+impl TryFrom<&pkcs8::SubjectPublicKeyInfoRef<'_>> for PublicKey {
+    type Error = pkcs8::spki::Error;
+
+    fn try_from(spki: &pkcs8::SubjectPublicKeyInfoRef<'_>) -> pkcs8::spki::Result<Self> {
+        spki.algorithm.assert_oids(ALGORITHM_OID, BignP256::OID)?;
+
+        let public_key_bytes = spki
+            .subject_public_key
+            .as_bytes()
+            .ok_or_else(|| der::Tag::BitString.value_error())?;
+
+        Self::from_bytes(public_key_bytes).map_err(|_| pkcs8::spki::Error::KeyMalformed)
+    }
+}
+
+#[cfg(all(feature = "alloc", feature = "pkcs8"))]
+impl EncodePublicKey for PublicKey {
+    fn to_public_key_der(&self) -> pkcs8::spki::Result<der::Document> {
+        let pk_bytes = self.to_bytes();
+        let subject_public_key = der::asn1::BitStringRef::new(0, &pk_bytes)?;
+
+        pkcs8::SubjectPublicKeyInfo {
+            algorithm: Self::ALGORITHM_IDENTIFIER,
+            subject_public_key,
+        }
+        .try_into()
+    }
+}
+
+impl From<PublicKey> for EncodedPoint {
+    fn from(value: PublicKey) -> Self {
+        value.point.to_encoded_point(false)
+    }
+}
+
+#[cfg(feature = "pem")]
+impl FromStr for PublicKey {
+    type Err = Error;
+
+    fn from_str(s: &str) -> Result<Self, Error> {
+        Self::from_public_key_pem(s).map_err(|_| Error)
+    }
+}
+
+#[cfg(feature = "pem")]
+impl Display for PublicKey {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(
+            f,
+            "{}",
+            self.to_public_key_pem(Default::default())
+                .expect("PEM encoding error")
+        )
+    }
+}
diff --git a/bign256/src/secret_key.rs b/bign256/src/secret_key.rs
new file mode 100644
index 000000000..00d111b9b
--- /dev/null
+++ b/bign256/src/secret_key.rs
@@ -0,0 +1,161 @@
+//! Bign256 secret key.
+
+use core::str::FromStr;
+use der::SecretDocument;
+
+use elliptic_curve::{array::typenum::Unsigned, zeroize::Zeroizing, Error};
+use pkcs8::{
+    spki::{AlgorithmIdentifier, AssociatedAlgorithmIdentifier},
+    AssociatedOid, DecodePrivateKey, EncodePrivateKey, ObjectIdentifier,
+};
+
+#[cfg(feature = "arithmetic")]
+use crate::FieldBytes;
+#[cfg(feature = "arithmetic")]
+use crate::{elliptic_curve::rand_core::CryptoRngCore, BignP256, NonZeroScalar, Result};
+use crate::{PublicKey, ScalarPrimitive, SecretKey, ALGORITHM_OID};
+
+impl SecretKey {
+    const MIN_SIZE: usize = 24;
+
+    /// Generate a random [`SecretKey`].
+    #[cfg(feature = "arithmetic")]
+    pub fn random(rng: &mut impl CryptoRngCore) -> Self {
+        Self {
+            inner: NonZeroScalar::random(rng).into(),
+        }
+    }
+
+    /// Borrow the inner secret [`elliptic_curve::ScalarPrimitive`] value.
+    ///
+    /// # ⚠️ Warning
+    ///
+    /// This value is key material.
+    ///
+    /// Please treat it with the care it deserves!
+    pub fn as_scalar_primitive(&self) -> &ScalarPrimitive {
+        &self.inner
+    }
+
+    /// Get the secret [`elliptic_curve::NonZeroScalar`] value for this key.
+    ///
+    /// # ⚠️ Warning
+    ///
+    /// This value is key material.
+    ///
+    /// Please treat it with the care it deserves!
+    #[cfg(feature = "arithmetic")]
+    pub fn to_nonzero_scalar(&self) -> NonZeroScalar {
+        (*self).into()
+    }
+
+    /// Get the [`PublicKey`] which corresponds to this secret key
+    #[cfg(feature = "arithmetic")]
+    pub fn public_key(&self) -> PublicKey {
+        PublicKey::from_secret_scalar(&self.to_nonzero_scalar())
+    }
+
+    /// Deserialize secret key from an encoded secret scalar.
+    pub fn from_bytes(bytes: &FieldBytes) -> Result<Self> {
+        let inner: ScalarPrimitive =
+            Option::from(ScalarPrimitive::from_bytes(bytes)).ok_or(Error)?;
+
+        if inner.is_zero().into() {
+            return Err(Error);
+        }
+
+        Ok(Self { inner })
+    }
+
+    /// Deserialize secret key from an encoded secret scalar passed as a byte slice.
+    ///
+    /// The slice is expected to be a minimum of 24-bytes (192-bytes) and at most
+    /// `C::FieldBytesSize` bytes in length.
+    ///
+    /// Byte slices shorter than the field size are handled by zero padding the input.
+    ///
+    /// NOTE: this function is variable-time with respect to the input length. To avoid a timing
+    /// sidechannel, always ensure that the input has been pre-padded to `C::FieldBytesSize`.
+    pub fn from_slice(slice: &[u8]) -> Result<Self> {
+        if slice.len() == <BignP256 as elliptic_curve::Curve>::FieldBytesSize::USIZE {
+            Self::from_bytes(FieldBytes::from_slice(slice))
+        } else if (Self::MIN_SIZE..<BignP256 as elliptic_curve::Curve>::FieldBytesSize::USIZE)
+            .contains(&slice.len())
+        {
+            let mut bytes = Zeroizing::new(FieldBytes::default());
+            let offset = <BignP256 as elliptic_curve::Curve>::FieldBytesSize::USIZE
+                .saturating_sub(slice.len());
+            bytes[offset..].copy_from_slice(slice);
+            Self::from_bytes(&bytes)
+        } else {
+            Err(Error)
+        }
+    }
+
+    /// Serialize raw secret scalar as a big endian integer.
+    pub fn to_bytes(&self) -> FieldBytes {
+        self.inner.to_bytes()
+    }
+}
+
+impl From<SecretKey> for NonZeroScalar {
+    fn from(secret_key: SecretKey) -> NonZeroScalar {
+        secret_key.to_nonzero_scalar()
+    }
+}
+
+#[cfg(feature = "arithmetic")]
+impl From<NonZeroScalar> for SecretKey {
+    fn from(scalar: NonZeroScalar) -> SecretKey {
+        SecretKey::from(&scalar)
+    }
+}
+
+#[cfg(feature = "arithmetic")]
+impl From<&NonZeroScalar> for SecretKey {
+    fn from(scalar: &NonZeroScalar) -> SecretKey {
+        SecretKey {
+            inner: scalar.into(),
+        }
+    }
+}
+
+impl AssociatedAlgorithmIdentifier for SecretKey {
+    type Params = ObjectIdentifier;
+    const ALGORITHM_IDENTIFIER: AlgorithmIdentifier<Self::Params> = AlgorithmIdentifier {
+        oid: ALGORITHM_OID,
+        parameters: Some(BignP256::OID),
+    };
+}
+
+impl TryFrom<pkcs8::PrivateKeyInfo<'_>> for SecretKey {
+    type Error = pkcs8::Error;
+
+    fn try_from(private_key_info: pkcs8::PrivateKeyInfo<'_>) -> pkcs8::Result<Self> {
+        private_key_info
+            .algorithm
+            .assert_oids(ALGORITHM_OID, BignP256::OID)?;
+        Self::from_slice(private_key_info.private_key).map_err(|_| pkcs8::Error::KeyMalformed)
+    }
+}
+
+#[cfg(feature = "pem")]
+impl FromStr for SecretKey {
+    type Err = Error;
+    fn from_str(s: &str) -> core::result::Result<Self, Error> {
+        Self::from_pkcs8_pem(s).map_err(|_| Error)
+    }
+}
+
+impl EncodePrivateKey for SecretKey {
+    fn to_pkcs8_der(&self) -> pkcs8::Result<SecretDocument> {
+        let algorithm_identifier = pkcs8::AlgorithmIdentifierRef {
+            oid: ALGORITHM_OID,
+            parameters: Some((&BignP256::OID).into()),
+        };
+
+        let ec_private_key = self.to_bytes();
+        let pkcs8_key = pkcs8::PrivateKeyInfo::new(algorithm_identifier, &ec_private_key);
+        Ok(SecretDocument::encode_msg(&pkcs8_key)?)
+    }
+}
diff --git a/bign256/tests/dsa.rs b/bign256/tests/dsa.rs
index 4ef5428db..96c88604a 100644
--- a/bign256/tests/dsa.rs
+++ b/bign256/tests/dsa.rs
@@ -1,23 +1,24 @@
 //! bign256 DSA Tests
 
-#![cfg(feature = "dsa")]
+#![cfg(feature = "ecdsa")]
+
+use elliptic_curve::ops::Reduce;
+use hex_literal::hex;
+use proptest::prelude::*;
 
 use bign256::{
-    dsa::{
+    ecdsa::{
         signature::{Signer, Verifier},
         Signature, SigningKey, VerifyingKey,
     },
     NonZeroScalar, Scalar, U256,
 };
-use elliptic_curve::ops::Reduce;
-use hex_literal::hex;
-use proptest::prelude::*;
 
-const PUBLIC_KEY: [u8; 65] = hex!(
-    "04
-    D07F8590A8F77BF84F1EF10C6DE44CF5DDD52B4C9DE4CE3FE0799D1750561ABD
-    909AD9B92A4DB89A4A050959DA2E0C1926281B466D68913417C8E86103A6C67A"
+const PUBLIC_KEY: [u8; 64] = hex!(
+    "BD1A5650 179D79E0 3FCEE49D 4C2BD5DD F54CE46D 0CF11E4F F87BF7A8 90857FD0"
+    "7AC6A603 61E8C817 3491686D 461B2826 190C2EDA 5909054A 9AB84D2A B9D99A90"
 );
+
 const MSG: &[u8] = b"testing";
 const SIG: [u8; 48] = hex!(
     "63F59C523FF1780851143114FFBC5C13"
@@ -26,7 +27,7 @@ const SIG: [u8; 48] = hex!(
 
 #[test]
 fn verify_test_vector() {
-    let vk = VerifyingKey::from_sec1_bytes(&PUBLIC_KEY).unwrap();
+    let vk = VerifyingKey::from_bytes(&PUBLIC_KEY).unwrap();
     let sig = Signature::try_from(&SIG).unwrap();
     assert!(vk.verify(MSG, &sig).is_ok());
 }
diff --git a/bign256/tests/ecdh.rs b/bign256/tests/ecdh.rs
new file mode 100644
index 000000000..7b070d8f3
--- /dev/null
+++ b/bign256/tests/ecdh.rs
@@ -0,0 +1,32 @@
+#![cfg(feature = "ecdh")]
+#[test]
+fn ecdh() {
+    use bign256::{ecdh::EphemeralSecret, EncodedPoint, PublicKey};
+    use rand_core::OsRng; // requires 'getrandom' feature
+
+    // Alice
+    let alice_secret = EphemeralSecret::random(&mut OsRng);
+    let alice_pk_bytes = EncodedPoint::from(alice_secret.public_key());
+
+    // Bob
+    let bob_secret = EphemeralSecret::random(&mut OsRng);
+    let bob_pk_bytes = EncodedPoint::from(bob_secret.public_key());
+
+    // Alice decodes Bob's serialized public key and computes a shared secret from it
+    let bob_public =
+        PublicKey::from_encoded_point(bob_pk_bytes).expect("bob's public key is invalid!"); // In real usage, don't panic, handle this!
+
+    let alice_shared = alice_secret.diffie_hellman(&bob_public);
+
+    // Bob decodes Alice's serialized public key and computes the same shared secret
+    let alice_public =
+        PublicKey::from_encoded_point(alice_pk_bytes).expect("alice's public key is invalid!"); // In real usage, don't panic, handle this!
+
+    let bob_shared = bob_secret.diffie_hellman(&alice_public);
+
+    // Both participants arrive on the same shared secret
+    assert_eq!(
+        alice_shared.raw_secret_bytes(),
+        bob_shared.raw_secret_bytes()
+    );
+}
diff --git a/bign256/tests/examples/pkcs8-private.der b/bign256/tests/examples/pkcs8-private.der
new file mode 100644
index 000000000..4b293bb76
Binary files /dev/null and b/bign256/tests/examples/pkcs8-private.der differ
diff --git a/bign256/tests/examples/pkcs8-private.pem b/bign256/tests/examples/pkcs8-private.pem
new file mode 100644
index 000000000..e581587f5
--- /dev/null
+++ b/bign256/tests/examples/pkcs8-private.pem
@@ -0,0 +1,4 @@
+-----BEGIN PRIVATE KEY-----
+MD8CAQAwGAYKKnAAAgAiZS0CAQYKKnAAAgAiZS0DAQQgH2a1uEtzOWdFM/AynHTy
+GDQoH+0HMkKeDHkjX8Jz4mk=
+-----END PRIVATE KEY-----
diff --git a/bign256/tests/examples/pkcs8-public.der b/bign256/tests/examples/pkcs8-public.der
new file mode 100644
index 000000000..f5b0cf251
Binary files /dev/null and b/bign256/tests/examples/pkcs8-public.der differ
diff --git a/bign256/tests/examples/pkcs8-public.pem b/bign256/tests/examples/pkcs8-public.pem
new file mode 100644
index 000000000..3495bfccc
--- /dev/null
+++ b/bign256/tests/examples/pkcs8-public.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MF0wGAYKKnAAAgAiZS0CAQYKKnAAAgAiZS0DAQNBALLYmXRs6y04kBzvQkY56jD9
+onIL58G6PwS8MV3yQSupOA6o7OD3p7p+qWUtusU7gnvSwvtZhIaY3i6mdZYF65Y=
+-----END PUBLIC KEY-----
diff --git a/bign256/tests/pkcs8.rs b/bign256/tests/pkcs8.rs
new file mode 100644
index 000000000..7d434a6f6
--- /dev/null
+++ b/bign256/tests/pkcs8.rs
@@ -0,0 +1,87 @@
+#![cfg(feature = "pkcs8")]
+
+use hex_literal::hex;
+use pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePrivateKey, EncodePublicKey};
+
+use bign256::{PublicKey, SecretKey};
+
+const PKCS8_PRIVATE_KEY_DER: &[u8; 65] = include_bytes!("examples/pkcs8-private.der");
+#[cfg(feature = "pem")]
+const PKCS8_PRIVATE_KEY_PEM: &str = include_str!("examples/pkcs8-private.pem");
+const PKCS8_PUBLIC_KEY_DER: &[u8; 95] = include_bytes!("examples/pkcs8-public.der");
+#[cfg(feature = "pem")]
+const PKCS8_PUBLIC_KEY_PEM: &str = include_str!("examples/pkcs8-public.pem");
+
+#[test]
+fn decode_pkcs8_private_key_from_der() {
+    let secret_key = SecretKey::from_pkcs8_der(&PKCS8_PRIVATE_KEY_DER[..]).unwrap();
+    let expected_scalar = hex!("1F66B5B84B7339674533F0329C74F21834281FED0732429E0C79235FC273E269");
+    assert_eq!(secret_key.to_bytes().as_slice(), &expected_scalar[..]);
+}
+
+#[test]
+fn decode_pkcs8_public_key_from_der() {
+    let public_key = PublicKey::from_public_key_der(&PKCS8_PUBLIC_KEY_DER[..]).unwrap();
+    let expected_point = hex!("\
+    B2 D8 99 74 6C EB 2D 38 90 1C EF 42 46 39 EA 30 FD A2 72 0B E7 C1 BA 3F 04 BC 31 5D F2 41 2B A9 \
+    38 0E A8 EC E0 F7 A7 BA 7E A9 65 2D BA C5 3B 82 7B D2 C2 FB 59 84 86 98 DE 2E A6 75 96 05 EB 96\
+    ");
+    assert_eq!(public_key.to_bytes().as_ref(), &expected_point[..]);
+}
+
+#[test]
+#[cfg(feature = "pem")]
+fn decode_pkcs8_private_key_from_pem() {
+    let secret_key = PKCS8_PRIVATE_KEY_PEM.parse::<SecretKey>().unwrap();
+
+    // Ensure key parses equivalently to DER
+    let der_key = SecretKey::from_pkcs8_der(&PKCS8_PRIVATE_KEY_DER[..]).unwrap();
+    assert_eq!(secret_key.to_bytes(), der_key.to_bytes());
+}
+
+#[test]
+#[cfg(feature = "pem")]
+fn decode_pkcs8_public_key_from_pem() {
+    let public_key = PKCS8_PUBLIC_KEY_PEM.parse::<PublicKey>().unwrap();
+
+    // Ensure key parses equivalently to DER
+    let der_key = PublicKey::from_public_key_der(&PKCS8_PUBLIC_KEY_DER[..]).unwrap();
+    assert_eq!(public_key, der_key);
+}
+
+#[test]
+#[cfg(feature = "pem")]
+fn encode_pkcs8_private_key_to_der() {
+    let original_secret_key = SecretKey::from_pkcs8_der(&PKCS8_PRIVATE_KEY_DER[..]).unwrap();
+    let reencoded_secret_key = original_secret_key.to_pkcs8_der();
+    assert_eq!(
+        reencoded_secret_key.unwrap().to_bytes().to_vec(),
+        &PKCS8_PRIVATE_KEY_DER[..]
+    );
+}
+
+#[test]
+#[cfg(feature = "pem")]
+fn encode_pkcs8_public_key_to_der() {
+    let original_public_key = PublicKey::from_public_key_der(&PKCS8_PUBLIC_KEY_DER[..]).unwrap();
+    let reencoded_public_key = original_public_key.to_public_key_der().unwrap();
+    assert_eq!(reencoded_public_key.as_ref(), &PKCS8_PUBLIC_KEY_DER[..]);
+}
+
+#[test]
+#[cfg(feature = "pem")]
+fn encode_pkcs8_private_key_to_pem() {
+    let original_secret_key = SecretKey::from_pkcs8_der(&PKCS8_PRIVATE_KEY_DER[..]).unwrap();
+    let reencoded_secret_key = original_secret_key
+        .to_pkcs8_pem(Default::default())
+        .unwrap();
+    assert_eq!(reencoded_secret_key.as_str(), PKCS8_PRIVATE_KEY_PEM);
+}
+
+#[test]
+#[cfg(feature = "pem")]
+fn encode_pkcs8_public_key_to_pem() {
+    let original_public_key = PublicKey::from_public_key_der(&PKCS8_PUBLIC_KEY_DER[..]).unwrap();
+    let reencoded_public_key = original_public_key.to_string();
+    assert_eq!(reencoded_public_key.as_str(), PKCS8_PUBLIC_KEY_PEM);
+}