Skip to content

Commit

Permalink
sm2: add dsa feature (#855)
Browse files Browse the repository at this point in the history
Implements SM2DSA, a digital signature algorithm specified in
GBT.32918.2-2016 as well as draft-shen-sm2-ecdsa.

The implementation draws a lot of inspiration from the `ecdsa` crate,
including the use of RFC6979 for deriving the ephemeral scalar `k`.

Tested against a signature test vector created using OpenSSL 3.0.8 using
the PKCS#8 example keys which were added in #848.
  • Loading branch information
tarcieri authored Apr 15, 2023
1 parent ec6b153 commit 655a7d6
Show file tree
Hide file tree
Showing 10 changed files with 734 additions and 10 deletions.
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 9 additions & 2 deletions sm2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,23 @@ elliptic-curve = { version = "0.13", default-features = false, features = ["hazm

# optional dependencies
primeorder = { version = "0.13.1", optional = true, path = "../primeorder" }
rfc6979 = { version = "0.4", optional = true }
signature = { version = "2", optional = true }
sm3 = { version = "0.4", optional = true, default-features = false }

[dev-dependencies]
hex-literal = "0.4"
proptest = "1"
rand_core = { version = "0.6", features = ["getrandom"] }

[features]
default = ["arithmetic", "pem", "std"]
alloc = ["elliptic-curve/alloc"]
std = ["alloc", "elliptic-curve/std"]
std = ["alloc", "elliptic-curve/std", "signature?/std"]

arithmetic = ["elliptic-curve/arithmetic", "primeorder"]
arithmetic = ["elliptic-curve/arithmetic", "dep:primeorder"]
dsa = ["arithmetic", "dep:rfc6979", "dep:signature", "dep:sm3"]
getrandom = ["rand_core/getrandom"]
pem = ["elliptic-curve/pem", "pkcs8"]
pkcs8 = ["elliptic-curve/pkcs8"]

Expand Down
5 changes: 2 additions & 3 deletions sm2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,9 @@ USE AT YOUR OWN RISK!
ShangMi 2 (SM2) is a Weierstrass curve specified in [GM/T 0003-2012]:
Cryptography Industry Standard of the People's Republic of China.

The SM2 cryptosystem is composed of three distinct algorithms, which are NOT
yet implemented by this crate:
The SM2 cryptosystem is composed of three distinct algorithms:

- [ ] **SM2DSA**: digital signature algorithm defined in [GBT.32918.2-2016], [ISO.IEC.14888-3] (SM2-2)
- [x] **SM2DSA**: digital signature algorithm defined in [GBT.32918.2-2016], [ISO.IEC.14888-3] (SM2-2)
- [ ] **SM2KEP**: key exchange protocol defined in [GBT.32918.3-2016] (SM2-3)
- [ ] **SM2PKE**: public key encryption algorithm defined in [GBT.32918.4-2016] (SM2-4)

Expand Down
4 changes: 4 additions & 0 deletions sm2/src/arithmetic/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
//! users may pick which license to apply.
#![allow(
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::cast_possible_truncation,
clippy::integer_arithmetic,
clippy::should_implement_trait,
clippy::suspicious_op_assign_impl,
clippy::unused_unit,
Expand Down
11 changes: 8 additions & 3 deletions sm2/src/arithmetic/scalar.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
//! SM2 scalar field elements.
#[cfg_attr(target_pointer_width = "32", path = "scalar/sm2_scalar_32.rs")]
#[cfg_attr(target_pointer_width = "64", path = "scalar/sm2_scalar_64.rs")]
#[allow(
#![allow(
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::cast_possible_truncation,
clippy::identity_op,
clippy::integer_arithmetic,
clippy::too_many_arguments,
clippy::unnecessary_cast
)]

#[cfg_attr(target_pointer_width = "32", path = "scalar/sm2_scalar_32.rs")]
#[cfg_attr(target_pointer_width = "64", path = "scalar/sm2_scalar_64.rs")]
mod scalar_impl;

use self::scalar_impl::*;
Expand Down
203 changes: 203 additions & 0 deletions sm2/src/dsa.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
//! SM2 Digital Signature Algorithm (SM2DSA) as defined in [draft-shen-sm2-ecdsa § 5].
//!
//! ## Usage
//!
//! NOTE: requires the `dsa` crate feature enabled, and `rand_core` dependency
//! with `getrandom` feature enabled.
//!
#![cfg_attr(feature = "std", doc = "```")]
#![cfg_attr(not(feature = "std"), doc = "```ignore")]
//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
//! use rand_core::OsRng; // requires 'getrandom` feature
//! use sm2::{
//! dsa::{Signature, SigningKey, signature::Signer},
//! SecretKey
//! };
//!
//! // Signing
//! let secret_key = SecretKey::random(&mut OsRng); // serialize with `::to_bytes()`
//! let dist_id = "example@rustcrypto.org"; // distinguishing identifier
//! let signing_key = SigningKey::new(dist_id, &secret_key)?;
//! let verifying_key_bytes = signing_key.verifying_key().to_sec1_bytes();
//! let message = b"test message";
//! let signature: Signature = signing_key.sign(message);
//!
//! // Verifying
//! use sm2::dsa::{VerifyingKey, signature::Verifier};
//!
//! let verifying_key = VerifyingKey::from_sec1_bytes(dist_id, &verifying_key_bytes)?;
//! verifying_key.verify(message, &signature)?;
//! # Ok(())
//! # }
//! ```
//!
//! [draft-shen-sm2-ecdsa § 5]: https://datatracker.ietf.org/doc/html/draft-shen-sm2-ecdsa-02#section-5
#[cfg(feature = "arithmetic")]
mod signing;
#[cfg(feature = "arithmetic")]
mod verifying;

pub use signature;

#[cfg(feature = "arithmetic")]
pub use self::{signing::SigningKey, verifying::VerifyingKey};

use crate::{FieldBytes, NonZeroScalar, Sm2};
use core::fmt::{self, Debug};
use elliptic_curve::generic_array::sequence::Concat;
use signature::{Error, Result, SignatureEncoding};

#[cfg(feature = "alloc")]
use alloc::vec::Vec;

/// SM2DSA signature serialized as bytes.
pub type SignatureBytes = [u8; Signature::BYTE_SIZE];

/// SM3 hash output.
type Hash = sm3::digest::Output<sm3::Sm3>;

/// Primitive scalar type (works without the `arithmetic` feature).
type ScalarPrimitive = elliptic_curve::ScalarPrimitive<Sm2>;

/// SM2DSA signature.
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct Signature {
r: ScalarPrimitive,
s: ScalarPrimitive,
}

impl Signature {
/// Size of an encoded SM2DSA signature in bytes.
pub const BYTE_SIZE: usize = 64;

/// Parse an SM2DSA signature from a byte array.
pub fn from_bytes(bytes: &SignatureBytes) -> Result<Self> {
let (r_bytes, s_bytes) = bytes.split_at(Self::BYTE_SIZE / 2);
let r = ScalarPrimitive::from_slice(r_bytes).map_err(|_| Error::new())?;
let s = ScalarPrimitive::from_slice(s_bytes).map_err(|_| Error::new())?;

if r.is_zero().into() || s.is_zero().into() {
return Err(Error::new());
}

Ok(Self { r, s })
}

/// Parse an SM2DSA signature from a byte slice.
pub fn from_slice(bytes: &[u8]) -> Result<Self> {
SignatureBytes::try_from(bytes)
.map_err(|_| Error::new())?
.try_into()
}

/// Create a [`Signature`] from the serialized `r` and `s` scalar values
/// which comprise the signature.
#[inline]
pub fn from_scalars(r: impl Into<FieldBytes>, s: impl Into<FieldBytes>) -> Result<Self> {
Self::try_from(r.into().concat(s.into()).as_slice())
}

/// Serialize this signature as bytes.
pub fn to_bytes(&self) -> SignatureBytes {
let mut ret = [0; Self::BYTE_SIZE];
let (r_bytes, s_bytes) = ret.split_at_mut(Self::BYTE_SIZE / 2);
r_bytes.copy_from_slice(&self.r.to_bytes());
s_bytes.copy_from_slice(&self.s.to_bytes());
ret
}

/// Bytes for the `R` component of a signature.
pub fn r_bytes(&self) -> FieldBytes {
self.r.to_bytes()
}

/// Bytes for the `s` component of a signature.
pub fn s_bytes(&self) -> FieldBytes {
self.s.to_bytes()
}

/// Convert this signature into a byte vector.
#[cfg(feature = "alloc")]
pub fn to_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
}

#[cfg(feature = "arithmetic")]
impl Signature {
/// Get the `r` component of this signature
pub fn r(&self) -> NonZeroScalar {
NonZeroScalar::new(self.r.into()).unwrap()
}

/// Get the `s` component of this signature
pub fn s(&self) -> NonZeroScalar {
NonZeroScalar::new(self.s.into()).unwrap()
}

/// Split the signature into its `r` and `s` scalars.
pub fn split_scalars(&self) -> (NonZeroScalar, NonZeroScalar) {
(self.r(), self.s())
}
}

impl Debug for Signature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "sm2::dsa::Signature(")?;

for byte in self.to_bytes() {
write!(f, "{:02X}", byte)?;
}

write!(f, ")")
}
}

impl From<Signature> for SignatureBytes {
fn from(signature: Signature) -> SignatureBytes {
signature.to_bytes()
}
}

impl From<&Signature> for SignatureBytes {
fn from(signature: &Signature) -> SignatureBytes {
signature.to_bytes()
}
}

impl SignatureEncoding for Signature {
type Repr = SignatureBytes;

fn to_bytes(&self) -> Self::Repr {
self.into()
}

fn encoded_len(&self) -> usize {
Self::BYTE_SIZE
}
}

impl TryFrom<SignatureBytes> for Signature {
type Error = Error;

fn try_from(signature: SignatureBytes) -> Result<Signature> {
Signature::from_bytes(&signature)
}
}

impl TryFrom<&SignatureBytes> for Signature {
type Error = Error;

fn try_from(signature: &SignatureBytes) -> Result<Signature> {
Signature::from_bytes(signature)
}
}

impl TryFrom<&[u8]> for Signature {
type Error = Error;

fn try_from(bytes: &[u8]) -> Result<Signature> {
Signature::from_slice(bytes)
}
}
Loading

0 comments on commit 655a7d6

Please sign in to comment.