Skip to content

Commit

Permalink
iqkms-signing: refactor into multiple modules (#30)
Browse files Browse the repository at this point in the history
Factors each major type into its own module.

Also adds some additional lints.
  • Loading branch information
tony-iqlusion authored Nov 29, 2022
1 parent 8697013 commit 9c8b1e0
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 145 deletions.
6 changes: 3 additions & 3 deletions iqkms-ethereum/src/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::Error;
use proto::ethereum::{signer_server::Signer, SignDigestRequest, SignEip155Request, Signature};
use signing::{
signature::{ecdsa::secp256k1::RecoverableSignature, hazmat::PrehashSigner},
KeyRing,
Keyring,
};
use tonic::{Request, Response, Status};
use tracing::trace;
Expand All @@ -16,12 +16,12 @@ type H256 = [u8; 32];
/// Signer gRPC service.
pub struct SignerService {
/// Signing keyring.
keyring: KeyRing,
keyring: Keyring,
}

impl SignerService {
/// Create a new RPC service with the given keyring.
pub fn new(keyring: KeyRing) -> Self {
pub fn new(keyring: Keyring) -> Self {
Self { keyring }
}

Expand Down
2 changes: 1 addition & 1 deletion iqkms-signing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ version = "0.0.1"
features = ["ecdsa", "getrandom", "sha2", "std"]
path = "../iq-crypto"

# optional dependencies
[dependencies]
# optional dependencies
types = { package = "iqkms-types", version = "0.0.1", optional = true, path = "../iqkms-types" }

[features]
Expand Down
2 changes: 1 addition & 1 deletion iqkms-signing/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

/// Error type.
// TODO(tarcieri): convert this into an enum?
#[derive(Clone, Debug)]
#[derive(Clone, Copy, Debug)]
pub struct Error;

/// Result type with the `iqkms-signing` crate's [`Error`] type.
Expand Down
70 changes: 70 additions & 0 deletions iqkms-signing/src/keyring.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use crate::{Error, Result, SigningKey, VerifyingKey};
use crypto::signature::ecdsa;
use std::{
collections::BTreeMap as Map,
fmt::{self, Debug},
};

#[cfg(feature = "ethereum")]
use types::ethereum;

/// Keys for producing digital signatures.
#[derive(Default)]
pub struct Keyring {
/// Signing keys.
keys: Map<VerifyingKey, SigningKey>,

/// Ethereum address index.
#[cfg(feature = "ethereum")]
eth_index: Map<ethereum::Address, VerifyingKey>,
}

impl Keyring {
/// Create a new key ring.
pub fn new() -> Self {
Self::default()
}

/// Add a key to the ring.
pub fn add(&mut self, signing_key: SigningKey) -> Result<()> {
let verifying_key = signing_key.verifying_key();

#[cfg(feature = "ethereum")]
#[allow(irrefutable_let_patterns)]
if let VerifyingKey::EcdsaSecp256k1(vk) = &verifying_key {
self.eth_index.insert(vk.try_into()?, verifying_key.clone());
}

if self.keys.insert(verifying_key, signing_key).is_some() {
Err(Error)
} else {
Ok(())
}
}

/// Find a key by its Ethereum address.
#[cfg(feature = "ethereum")]
#[cfg_attr(docsrs, doc(cfg(feature = "ethereum")))]
pub fn find_by_eth_address(
&self,
addr: &ethereum::Address,
) -> Result<&ecdsa::secp256k1::SigningKey> {
let signing_key = self
.eth_index
.get(addr)
.and_then(|vk| self.keys.get(vk))
.ok_or(Error)?;

match signing_key {
SigningKey::EcdsaSecp256k1(key) => Ok(key),
#[allow(unreachable_patterns)]
_ => Err(Error),
}
}
}

impl Debug for Keyring {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Keyring").finish_non_exhaustive()
}
}
156 changes: 17 additions & 139 deletions iqkms-signing/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//! iqkms keyring: data structure which stores available keys.
//! iqkms signing: service and keyring manager for producing digital signatures
//! using keys stored in iqkms.

#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc = include_str!("../README.md")]
Expand All @@ -8,152 +9,29 @@
#![forbid(unsafe_code)]
#![warn(
clippy::integer_arithmetic,
clippy::mod_module_files,
clippy::panic,
clippy::panic_in_result_fn,
clippy::unwrap_used,
future_incompatible,
missing_docs,
missing_debug_implementations,
rust_2018_idioms,
trivial_casts,
trivial_numeric_casts,
unused_lifetimes,
unused_qualifications
)]

mod error;

pub use crate::error::{Error, Result};
pub use crypto::{
digest::{sha2::Sha256, Digest},
rand::{OsRng, RngCore},
signature,
mod keyring;
mod signing_key;
mod verifying_key;

pub use crate::{
error::{Error, Result},
keyring::Keyring,
signing_key::SigningKey,
verifying_key::VerifyingKey,
};

use crypto::signature::{ecdsa, hazmat::PrehashSigner};
use std::collections::BTreeMap as Map;

#[cfg(feature = "ethereum")]
use types::ethereum;

/// Keys for producing digital signatures.
#[derive(Default)]
pub struct KeyRing {
/// Signing keys.
keys: Map<VerifyingKey, SigningKey>,

/// Ethereum address index.
#[cfg(feature = "ethereum")]
eth_index: Map<ethereum::Address, VerifyingKey>,
}

impl KeyRing {
/// Create a new key ring.
pub fn new() -> Self {
Self::default()
}

/// Add a key to the ring.
pub fn add(&mut self, signing_key: SigningKey) -> Result<()> {
let verifying_key = signing_key.verifying_key();

#[cfg(feature = "ethereum")]
#[allow(irrefutable_let_patterns)]
if let VerifyingKey::EcdsaSecp256k1(vk) = &verifying_key {
self.eth_index.insert(vk.try_into()?, verifying_key.clone());
}

if self.keys.insert(verifying_key, signing_key).is_some() {
Err(Error)
} else {
Ok(())
}
}

/// Find a key by its Ethereum address.
#[cfg(feature = "ethereum")]
#[cfg_attr(docsrs, doc(cfg(feature = "ethereum")))]
pub fn find_by_eth_address(
&self,
addr: &ethereum::Address,
) -> Result<&ecdsa::secp256k1::SigningKey> {
let signing_key = self
.eth_index
.get(addr)
.and_then(|vk| self.keys.get(vk))
.ok_or(Error)?;

match signing_key {
SigningKey::EcdsaSecp256k1(key) => Ok(key),
#[allow(unreachable_patterns)]
_ => Err(Error),
}
}
}

/// Signing key types.
pub enum SigningKey {
/// ECDSA/secp256k1
#[cfg(feature = "secp256k1")]
#[cfg_attr(docsrs, doc(cfg(feature = "secp256k1")))]
EcdsaSecp256k1(ecdsa::secp256k1::SigningKey),
}

impl SigningKey {
/// Generate a random ECDSA/secp256k1 key.
// TODO(tarcieri): unified `generate` method with algorithm parameter
#[cfg(feature = "secp256k1")]
#[cfg_attr(docsrs, doc(cfg(feature = "secp256k1")))]
pub fn generate_secp256k1() -> Self {
let mut bytes = [0u8; 32];

loop {
OsRng.fill_bytes(&mut bytes);

if let Ok(signing_key) = ecdsa::secp256k1::SigningKey::from_bytes(&bytes) {
// TODO(tarcieri): zeroize bytes
return signing_key.into();
}
}
}

/// Sign the given message with this key.
// TODO(tarcieri): support for customizing hash function used
pub fn sign(&self, msg: &[u8]) -> Result<Vec<u8>> {
self.sign_digest(&Sha256::digest(msg))
}

/// Sign the given prehashed message digest with this key.
pub fn sign_digest(&self, msg_digest: &[u8]) -> Result<Vec<u8>> {
match self {
#[cfg(feature = "secp256k1")]
Self::EcdsaSecp256k1(sk) => {
PrehashSigner::<ecdsa::secp256k1::Signature>::sign_prehash(sk, msg_digest)
.map(|sig| sig.to_vec())
.map_err(|_| Error)
}
}
}

/// Get the [`VerifyingKey`] that corresponds to this signing key.
pub fn verifying_key(&self) -> VerifyingKey {
match self {
#[cfg(feature = "secp256k1")]
SigningKey::EcdsaSecp256k1(sk) => VerifyingKey::EcdsaSecp256k1(sk.verifying_key()),
}
}
}

#[cfg(feature = "secp256k1")]
#[cfg_attr(docsrs, doc(cfg(feature = "secp256k1")))]
impl From<ecdsa::secp256k1::SigningKey> for SigningKey {
#[inline]
fn from(key: ecdsa::secp256k1::SigningKey) -> SigningKey {
SigningKey::EcdsaSecp256k1(key)
}
}

/// Verifying key types.
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum VerifyingKey {
/// ECDSA/secp256k1
#[cfg(feature = "secp256k1")]
#[cfg_attr(docsrs, doc(cfg(feature = "secp256k1")))]
EcdsaSecp256k1(ecdsa::secp256k1::VerifyingKey),
}
pub use crypto::signature;
77 changes: 77 additions & 0 deletions iqkms-signing/src/signing_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use crate::{Error, Result, VerifyingKey};
use crypto::{
digest::{sha2::Sha256, Digest},
rand::{OsRng, RngCore},
signature::{ecdsa, hazmat::PrehashSigner},
};
use std::fmt::{self, Debug};

/// Signing key.
pub enum SigningKey {
/// ECDSA/secp256k1
#[cfg(feature = "secp256k1")]
#[cfg_attr(docsrs, doc(cfg(feature = "secp256k1")))]
EcdsaSecp256k1(ecdsa::secp256k1::SigningKey),
}

impl SigningKey {
/// Generate a random ECDSA/secp256k1 key.
// TODO(tarcieri): unified `generate` method with algorithm parameter
#[cfg(feature = "secp256k1")]
#[cfg_attr(docsrs, doc(cfg(feature = "secp256k1")))]
pub fn generate_secp256k1() -> Self {
let mut bytes = [0u8; 32];

loop {
OsRng.fill_bytes(&mut bytes);

if let Ok(signing_key) = ecdsa::secp256k1::SigningKey::from_bytes(&bytes) {
// TODO(tarcieri): zeroize bytes
return signing_key.into();
}
}
}

/// Sign the given message with this key.
// TODO(tarcieri): support for customizing hash function used
pub fn sign(&self, msg: &[u8]) -> Result<Vec<u8>> {
self.sign_digest(&Sha256::digest(msg))
}

/// Sign the given prehashed message digest with this key.
pub fn sign_digest(&self, msg_digest: &[u8]) -> Result<Vec<u8>> {
match self {
#[cfg(feature = "secp256k1")]
Self::EcdsaSecp256k1(sk) => {
PrehashSigner::<ecdsa::secp256k1::Signature>::sign_prehash(sk, msg_digest)
.map(|sig| sig.to_vec())
.map_err(|_| Error)
}
}
}

/// Get the [`VerifyingKey`] that corresponds to this signing key.
pub fn verifying_key(&self) -> VerifyingKey {
match self {
#[cfg(feature = "secp256k1")]
SigningKey::EcdsaSecp256k1(sk) => VerifyingKey::EcdsaSecp256k1(sk.verifying_key()),
}
}
}

impl Debug for SigningKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SigningKey")
.field("verifying_key", &self.verifying_key())
.finish_non_exhaustive()
}
}

#[cfg(feature = "secp256k1")]
#[cfg_attr(docsrs, doc(cfg(feature = "secp256k1")))]
impl From<ecdsa::secp256k1::SigningKey> for SigningKey {
#[inline]
fn from(key: ecdsa::secp256k1::SigningKey) -> SigningKey {
SigningKey::EcdsaSecp256k1(key)
}
}
10 changes: 10 additions & 0 deletions iqkms-signing/src/verifying_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use crypto::signature::ecdsa;

/// Verifying key.
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum VerifyingKey {
/// ECDSA/secp256k1
#[cfg(feature = "secp256k1")]
#[cfg_attr(docsrs, doc(cfg(feature = "secp256k1")))]
EcdsaSecp256k1(ecdsa::secp256k1::VerifyingKey),
}
2 changes: 1 addition & 1 deletion iqkmsd/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use tonic::transport::Server;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "[::1]:27100".parse().unwrap();
let keyring = signing::KeyRing::new();
let keyring = signing::Keyring::new();
let eth_service = ethereum::SignerService::new(keyring);

println!("Listening on {}", addr);
Expand Down

0 comments on commit 9c8b1e0

Please sign in to comment.