mirror of
https://github.com/RustCrypto/signatures
synced 2026-06-21 13:45:42 +00:00
signature_derive: Custom derive support for Signer/Verifier
We've explored several different possible trait bounds for a blanket impl of `Signer` for `DigestSigner` (and likewise for `Verifier`). Unfortunately, the approach we netted out at in #12 does not permit anything but the blanket impl, and was removed in #16. It's definitely still worth exploring if there's a way to define the bounds of the default impl that works, however there is one approach we haven't explored yet: a procedural macro. This approach feels like a bit of a hack, but gets us to where we originally wanted to be with the blanket impl API-wise. The impl it derives uses the same approach and bounds as the blanket impl did: the `Digest` to use is sourced from an associated type of the `DigestSignature` trait, which means the digest to use is both automatic and ensures the type deriving the trait supports the expected `Digest` for the given signature. The implementation uses `synstructure` which simplifies both handling of generics and testing the output of the proc macro. Additionally, it includes a complete integration test of the derived code which ensures it works as expected. It's gated under a cargo feature and disabled-by-default. This should make it unobtrusive for downstream crates which don't need its functionality.
This commit is contained in:
@@ -15,6 +15,7 @@ install:
|
||||
|
||||
script:
|
||||
- cargo test --verbose --release
|
||||
- cargo test --verbose --all-features --release
|
||||
# Can't use `--no-default-features` with a workspace. See rust-lang/cargo#4753
|
||||
- cd signature-crate && cargo build --no-default-features --release
|
||||
|
||||
|
||||
@@ -3,4 +3,5 @@ members = [
|
||||
"ecdsa",
|
||||
"ed25519",
|
||||
"signature-crate",
|
||||
"signature-crate/signature_derive"
|
||||
]
|
||||
|
||||
@@ -13,6 +13,11 @@ categories = ["cryptography", "no-std"]
|
||||
|
||||
[dependencies]
|
||||
digest = { version = "0.8", optional = true, default-features = false }
|
||||
signature_derive = { version = "0", optional = true, path = "signature_derive" }
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = "0.2"
|
||||
sha2 = { version = "0.8", default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["digest", "std"]
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
# `signature` crate
|
||||
|
||||
[](https://travis-ci.org/RustCrypto/signatures)
|
||||
[![crate][crate-image]][crate-link]
|
||||
[![Docs][docs-image]][docs-link]
|
||||
![Apache2/MIT licensed][license-image]
|
||||
![Rust Version][rustc-image]
|
||||
[![Build Status][build-image]][build-link]
|
||||
|
||||
This crate contains traits which provide generic, object-safe APIs for
|
||||
generating and verifying [digital signatures][1].
|
||||
generating and verifying [digital signatures].
|
||||
|
||||
The long-term goal is to use this crate in conjunction with the
|
||||
[`ecdsa`][ecdsa-crate] and [`ed25519`][ed25519-crate], however those crates
|
||||
are a work-in-progress.
|
||||
|
||||
[Documentation][docs-link]
|
||||
|
||||
## License
|
||||
|
||||
@@ -20,4 +30,19 @@ Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
|
||||
dual licensed as above, without any additional terms or conditions.
|
||||
|
||||
[1]: https://en.wikipedia.org/wiki/Digital_signature
|
||||
[//]: # (badges)
|
||||
|
||||
[crate-image]: https://img.shields.io/crates/v/signature.svg
|
||||
[crate-link]: https://crates.io/crates/signature
|
||||
[docs-image]: https://docs.rs/signature/badge.svg
|
||||
[docs-link]: https://docs.rs/signature/
|
||||
[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg
|
||||
[rustc-image]: https://img.shields.io/badge/rustc-1.31+-blue.svg
|
||||
[build-image]: https://travis-ci.org/RustCrypto/signatures.svg?branch=master
|
||||
[build-link]: https://travis-ci.org/RustCrypto/signatures
|
||||
|
||||
[//]: # (general links)
|
||||
|
||||
[digital signatures]: https://en.wikipedia.org/wiki/Digital_signature
|
||||
[ecdsa-crate]: https://github.com/RustCrypto/signatures/tree/master/ecdsa
|
||||
[ed25519-crate]: https://github.com/RustCrypto/signatures/tree/master/ed25519
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "signature_derive"
|
||||
version = "0.0.0"
|
||||
authors = ["RustCrypto Developers"]
|
||||
license = "Apache-2.0 OR MIT"
|
||||
description = "Custom derive support for the 'signature' crate"
|
||||
documentation = "https://docs.rs/signature"
|
||||
repository = "https://github.com/RustCrypto/signatures/tree/master/signature-crate/"
|
||||
readme = "README.md"
|
||||
edition = "2018"
|
||||
keywords = ["crypto", "ecdsa", "ed25519", "signature", "signing"]
|
||||
categories = ["cryptography", "no-std"]
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "0.4"
|
||||
quote = "0.6"
|
||||
syn = "0.15"
|
||||
synstructure = "0.10"
|
||||
@@ -0,0 +1,27 @@
|
||||
# `signature` crate custom derive support
|
||||
|
||||
[](https://travis-ci.org/RustCrypto/signatures)
|
||||
|
||||
This crate provides proc macros used by the `signature` crate.
|
||||
|
||||
It's not intended to be used directly. See the signature crate's documentation
|
||||
for additional details:
|
||||
|
||||
[Documentation][docs]
|
||||
|
||||
## License
|
||||
|
||||
All crates licensed under either of
|
||||
|
||||
* [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
* [MIT license](http://opensource.org/licenses/MIT)
|
||||
|
||||
at your option.
|
||||
|
||||
### Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
|
||||
dual licensed as above, without any additional terms or conditions.
|
||||
|
||||
[Documentation]: https://docs.rs/signature/
|
||||
@@ -0,0 +1,115 @@
|
||||
//! Custom derive support for the `signature` crate.
|
||||
//!
|
||||
//! This crate can be used to derive `Signer` and `Verifier` impls for
|
||||
//! types that impl `DigestSigner` or `DigestVerifier` respectively.
|
||||
|
||||
#![crate_type = "proc-macro"]
|
||||
#![recursion_limit = "128"]
|
||||
#![deny(warnings, unused_import_braces, unused_qualifications)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use synstructure::{decl_derive, AddBounds};
|
||||
|
||||
/// Derive the `Signer` trait for `DigestSigner` types
|
||||
fn derive_signer(mut s: synstructure::Structure) -> TokenStream {
|
||||
s.add_bounds(AddBounds::None);
|
||||
s.gen_impl(quote! {
|
||||
use signature::{DigestSignature, DigestSigner, Error};
|
||||
|
||||
gen impl<S> Signer<S> for @Self
|
||||
where
|
||||
S: DigestSignature,
|
||||
Self: DigestSigner<S::Digest, S>,
|
||||
{
|
||||
fn try_sign(&self, msg: &[u8]) -> Result<S, Error> {
|
||||
self.try_sign_digest(S::Digest::new().chain(msg))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
decl_derive!([Signer] => derive_signer);
|
||||
|
||||
/// Derive the `Verifier` trait for `DigestVerifier` types
|
||||
fn derive_verifier(mut s: synstructure::Structure) -> TokenStream {
|
||||
s.add_bounds(AddBounds::None);
|
||||
s.gen_impl(quote! {
|
||||
use signature::{DigestSignature, DigestVerifier, Error};
|
||||
|
||||
gen impl<S> Verifier<S> for @Self
|
||||
where
|
||||
S: DigestSignature,
|
||||
Self: DigestVerifier<S::Digest, S>,
|
||||
{
|
||||
fn verify(&self, msg: &[u8], signature: &S) -> Result<(), Error> {
|
||||
self.verify_digest(S::Digest::new().chain(msg), signature)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
decl_derive!([Verifier] => derive_verifier);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use synstructure::test_derive;
|
||||
|
||||
#[test]
|
||||
fn signer() {
|
||||
test_derive! {
|
||||
derive_signer {
|
||||
struct MySigner<C: EllipticCurve> {
|
||||
scalar: Scalar<C::ScalarSize>
|
||||
}
|
||||
}
|
||||
expands to {
|
||||
#[allow(non_upper_case_globals)]
|
||||
const _DERIVE_Signer_S_FOR_MySigner: () = {
|
||||
use signature::{DigestSignature, DigestSigner, Error};
|
||||
|
||||
impl<S, C: EllipticCurve> Signer<S> for MySigner<C>
|
||||
where
|
||||
S: DigestSignature,
|
||||
Self: DigestSigner<S::Digest, S>,
|
||||
{
|
||||
fn try_sign(&self, msg: &[u8]) -> Result <S, Error> {
|
||||
self.try_sign_digest(S::Digest::new().chain(msg))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
no_build // tests in `signature-crate/tests`
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifier() {
|
||||
test_derive! {
|
||||
derive_verifier {
|
||||
struct MyVerifier<C: EllipticCurve> {
|
||||
point: UncompressedPoint<C>
|
||||
}
|
||||
}
|
||||
expands to {
|
||||
#[allow(non_upper_case_globals)]
|
||||
const _DERIVE_Verifier_S_FOR_MyVerifier: () = {
|
||||
use signature::{DigestSignature, DigestVerifier, Error};
|
||||
|
||||
impl<S, C: EllipticCurve> Verifier<S> for MyVerifier<C>
|
||||
where
|
||||
S: DigestSignature,
|
||||
Self: DigestVerifier<S::Digest, S>,
|
||||
{
|
||||
fn verify(&self, msg: &[u8], signature: &S) -> Result<(), Error> {
|
||||
self.verify_digest(S::Digest::new().chain(msg), signature)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
no_build // tests in `signature-crate/tests`
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,17 @@
|
||||
#[macro_use]
|
||||
extern crate std;
|
||||
|
||||
#[cfg(feature = "signature_derive")]
|
||||
#[allow(unused_imports)]
|
||||
#[macro_use]
|
||||
extern crate signature_derive;
|
||||
|
||||
#[cfg(feature = "signature_derive")]
|
||||
#[doc(hidden)]
|
||||
pub use signature_derive::{Signer, Verifier};
|
||||
|
||||
#[cfg(feature = "digest")]
|
||||
pub extern crate digest;
|
||||
pub use digest;
|
||||
|
||||
mod error;
|
||||
mod prelude;
|
||||
|
||||
@@ -22,3 +22,19 @@ pub trait Signature: AsRef<[u8]> + Debug + Sized {
|
||||
self.as_slice().into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Marker trait for `Signature` types computable as `S(H(m))`
|
||||
///
|
||||
/// - `S`: signature algorithm
|
||||
/// - `H`: hash (a.k.a. digest) function
|
||||
/// - `m`: message
|
||||
///
|
||||
/// For signature types that implement this trait, a blanket impl of
|
||||
/// `Signer` will be provided for all types that `impl DigestSigner`
|
||||
/// along with a corresponding impl of `Verifier` for all types that
|
||||
/// `impl DigestVerifier`.
|
||||
#[cfg(feature = "digest")]
|
||||
pub trait DigestSignature: Signature {
|
||||
/// Preferred `Digest` algorithm to use when computing this signature type.
|
||||
type Digest: digest::Digest;
|
||||
}
|
||||
|
||||
@@ -27,19 +27,6 @@ where
|
||||
D: Digest,
|
||||
S: Signature,
|
||||
{
|
||||
/// Sign the computed digest of the given message.
|
||||
///
|
||||
/// Panics in the event of a signing error.
|
||||
fn sign_msg_digest(&self, msg: &[u8]) -> S {
|
||||
self.try_sign_msg_digest(msg)
|
||||
.expect("signature operation failed")
|
||||
}
|
||||
|
||||
/// Attempt to sign the computed digest of the given message.
|
||||
fn try_sign_msg_digest(&self, msg: &[u8]) -> Result<S, Error> {
|
||||
self.try_sign_digest(D::new().chain(msg))
|
||||
}
|
||||
|
||||
/// Sign the given prehashed message `Digest`, returning a signature.
|
||||
///
|
||||
/// Panics in the event of a signing error.
|
||||
|
||||
@@ -21,11 +21,6 @@ where
|
||||
D: Digest,
|
||||
S: Signature,
|
||||
{
|
||||
/// Verify the signature against the computed `Digest` output.
|
||||
fn verify_msg_digest(&self, msg: &[u8], signature: &S) -> Result<(), Error> {
|
||||
self.verify_digest(D::new().chain(msg), signature)
|
||||
}
|
||||
|
||||
/// Verify the signature against the given `Digest` output.
|
||||
fn verify_digest(&self, digest: D, signature: &S) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/// "Tests" for code generated by `signature_derive`
|
||||
#[cfg(all(test, feature = "signature_derive"))]
|
||||
mod tests {
|
||||
use digest::{generic_array::GenericArray, Digest};
|
||||
use hex_literal::hex;
|
||||
use sha2::Sha256;
|
||||
use signature::{
|
||||
DigestSignature, DigestSigner, DigestVerifier, Error, Signature, Signer, Verifier,
|
||||
};
|
||||
|
||||
/// Test vector to compute SHA-256 digest of
|
||||
const INPUT_STRING: &[u8] = b"abc";
|
||||
|
||||
/// Expected SHA-256 digest for the input string
|
||||
const INPUT_STRING_DIGEST: [u8; 32] =
|
||||
hex!("ba7816bf 8f01cfea 414140de 5dae2223 b00361a3 96177a9c b410ff61 f20015ad");
|
||||
|
||||
/// Dummy signature which just contains a digest output
|
||||
#[derive(Debug)]
|
||||
struct DummySignature(GenericArray<u8, <Sha256 as Digest>::OutputSize>);
|
||||
|
||||
impl Signature for DummySignature {
|
||||
fn from_bytes<B: AsRef<[u8]>>(bytes: B) -> Result<Self, Error> {
|
||||
Ok(DummySignature(GenericArray::clone_from_slice(
|
||||
bytes.as_ref(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for DummySignature {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl DigestSignature for DummySignature {
|
||||
type Digest = Sha256;
|
||||
}
|
||||
|
||||
/// Dummy signer which just returns the message digest as a `DummySignature`
|
||||
#[derive(Signer, Default)]
|
||||
struct DummySigner {}
|
||||
|
||||
impl DigestSigner<Sha256, DummySignature> for DummySigner {
|
||||
fn try_sign_digest(&self, digest: Sha256) -> Result<DummySignature, Error> {
|
||||
DummySignature::from_bytes(digest.result())
|
||||
}
|
||||
}
|
||||
|
||||
/// Dummy verifier which ensures the `DummySignature` digest matches the
|
||||
/// expected value.
|
||||
///
|
||||
/// Panics (via `assert_eq!`) if the value is not what is expected.
|
||||
#[derive(Verifier, Default)]
|
||||
struct DummyVerifier {}
|
||||
|
||||
impl DigestVerifier<Sha256, DummySignature> for DummyVerifier {
|
||||
fn verify_digest(&self, digest: Sha256, signature: &DummySignature) -> Result<(), Error> {
|
||||
let actual_digest = digest.result();
|
||||
assert_eq!(signature.as_ref(), actual_digest.as_ref());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derived_signer_impl() {
|
||||
let sig: DummySignature = DummySigner::default().sign(INPUT_STRING);
|
||||
assert_eq!(sig.as_ref(), INPUT_STRING_DIGEST.as_ref())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derived_verifier_impl() {
|
||||
let sig: DummySignature = DummySigner::default().sign(INPUT_STRING);
|
||||
assert!(DummyVerifier::default().verify(INPUT_STRING, &sig).is_ok());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user