Skip to content
This repository has been archived by the owner on Jun 3, 2020. It is now read-only.

Commit

Permalink
[WIP] ECDSA (secp256k1) key support
Browse files Browse the repository at this point in the history
Support for ECDSA (secp256k1) account keys, with the goal of using them
to support KMS-backed Cosmos transaction signing (#386)
  • Loading branch information
tony-iqlusion committed Jan 24, 2020
1 parent 520727f commit 9fccdb2
Show file tree
Hide file tree
Showing 13 changed files with 309 additions and 49 deletions.
21 changes: 20 additions & 1 deletion Cargo.lock

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

11 changes: 6 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,17 @@ tendermint = "= 0.12.0-rc0"
thiserror = "1"
wait-timeout = "0.2"
x25519-dalek = "0.6"
yubihsm = { version = "0.31", features = ["setup", "usb"], optional = true }
zeroize = "1"

[dependencies.yubihsm]
version = "0.31"
optional = true
features = ["secp256k1", "setup", "usb"]

[dev-dependencies]
abscissa_core = { version = "0.5", features = ["testing"] }
tempfile = "3"

[dev-dependencies.abscissa_core]
version = "0.5"
features = ["testing"]

[features]
default = []
softsign = []
Expand Down
32 changes: 12 additions & 20 deletions src/chain/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
use super::{Chain, Guard, Id};
use crate::{
error::{Error, ErrorKind::*},
keyring,
prelude::*,
};
use once_cell::sync::Lazy;
Expand All @@ -17,25 +16,6 @@ pub static REGISTRY: Lazy<GlobalRegistry> = Lazy::new(GlobalRegistry::default);
pub struct Registry(BTreeMap<Id, Chain>);

impl Registry {
/// Add a key to a keyring for a chain stored in the registry
pub fn add_to_keyring(
&mut self,
chain_id: &Id,
signer: keyring::ed25519::Signer,
) -> Result<(), Error> {
// TODO(tarcieri):
let chain = self.0.get_mut(chain_id).ok_or_else(|| {
format_err!(
InvalidKey,
"can't add signer {} to unregistered chain: {}",
signer.provider(),
chain_id
)
})?;

chain.keyring.add(signer)
}

/// Register a `Chain` with the registry
pub fn register_chain(&mut self, chain: Chain) -> Result<(), Error> {
let chain_id = chain.id;
Expand All @@ -52,6 +32,18 @@ impl Registry {
pub fn get_chain(&self, chain_id: &Id) -> Option<&Chain> {
self.0.get(chain_id)
}

/// Get a mutable reference to the given chain
pub(crate) fn get_chain_mut(&mut self, chain_id: &Id) -> Result<&mut Chain, Error> {
self.0.get_mut(chain_id).ok_or_else(|| {
format_err!(
InvalidKey,
"can't add signer to unregistered chain: {}",
chain_id
)
.into()
})
}
}

/// Global registry of blockchain networks known to the KMS
Expand Down
18 changes: 18 additions & 0 deletions src/config/provider/softsign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::{
};
use serde::Deserialize;
use std::{
fmt::{self, Display},
path::{Path, PathBuf},
str::FromStr,
};
Expand Down Expand Up @@ -54,13 +55,30 @@ pub enum KeyFormat {
Json,
}

impl KeyFormat {
/// Get a string reference describing this key format
pub fn as_str(&self) -> &str {
match self {
KeyFormat::Raw => "raw",
KeyFormat::Base64 => "base64",
KeyFormat::Json => "json",
}
}
}

impl Default for KeyFormat {
fn default() -> Self {
// TODO(tarcieri): change to Base64
KeyFormat::Raw
}
}

impl Display for KeyFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}

impl FromStr for KeyFormat {
type Err = Error;

Expand Down
95 changes: 77 additions & 18 deletions src/keyring.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
//! Signing keyring. Presently specialized for Ed25519.
pub mod ecdsa;
pub mod ed25519;
pub mod format;
pub mod providers;

use self::ed25519::Signer;
pub use self::{format::Format, providers::SigningProvider};
use crate::{
chain,
config::provider::ProviderConfig,
error::{Error, ErrorKind::*},
prelude::*,
};
use std::collections::BTreeMap;
use std::collections::BTreeMap as Map;
use subtle_encoding;
use tendermint::TendermintKey;

Expand All @@ -21,8 +21,11 @@ pub type SecretKeyEncoding = subtle_encoding::Base64;

/// Signing keyring
pub struct KeyRing {
/// Keys in the keyring
keys: BTreeMap<TendermintKey, Signer>,
/// ECDSA (secp256k1) keys in the keyring
ecdsa_keys: Map<TendermintKey, ecdsa::Signer>,

/// Ed25519 keys in the keyring
ed25519_keys: Map<TendermintKey, ed25519::Signer>,

/// Formatting configuration when displaying keys (e.g. bech32)
format: Format,
Expand All @@ -32,14 +35,44 @@ impl KeyRing {
/// Create a new keyring
pub fn new(format: Format) -> Self {
Self {
keys: BTreeMap::new(),
ecdsa_keys: Map::new(),
ed25519_keys: Map::new(),
format,
}
}

/// Add a key to the keyring, returning an error if we already have a
/// signer registered for the given public key
pub fn add(&mut self, signer: Signer) -> Result<(), Error> {
/// Add an ECDSA key to the keyring, returning an error if we already
/// have a signer registered for the given public key.
pub fn add_ecdsa(&mut self, signer: ecdsa::Signer) -> Result<(), Error> {
let provider = signer.provider();
let public_key = signer.public_key();
let public_key_serialized = self.format.serialize(public_key);
let key_type = match public_key {
TendermintKey::AccountKey(_) => "account",
TendermintKey::ConsensusKey(_) => "consensus",
};

info!(
"[keyring:{}] added ECDSA {} key {}",
provider, key_type, public_key_serialized
);

if let Some(other) = self.ecdsa_keys.insert(public_key, signer) {
fail!(
InvalidKey,
"[keyring:{}] duplicate ECDSA key {} already registered as {}",
provider,
public_key_serialized,
other.provider(),
)
} else {
Ok(())
}
}

/// Add an Ed25519 key to the keyring, returning an error if we already
/// have a signer registered for the given public key.
pub fn add_ed25519(&mut self, signer: ed25519::Signer) -> Result<(), Error> {
let provider = signer.provider();
let public_key = signer.public_key();
let public_key_serialized = self.format.serialize(public_key);
Expand All @@ -49,14 +82,14 @@ impl KeyRing {
};

info!(
"[keyring:{}] added {} key {}",
"[keyring:{}] added Ed25519 {} key {}",
provider, key_type, public_key_serialized
);

if let Some(other) = self.keys.insert(public_key, signer) {
if let Some(other) = self.ed25519_keys.insert(public_key, signer) {
fail!(
InvalidKey,
"[keyring:{}] duplicate key {} already registered as {}",
"[keyring:{}] duplicate Ed25519 key {} already registered as {}",
provider,
public_key_serialized,
other.provider(),
Expand All @@ -66,9 +99,9 @@ impl KeyRing {
}
}

/// Get the default public key for this keyring
pub fn default_pubkey(&self) -> Result<TendermintKey, Error> {
let mut keys = self.keys.keys();
/// Get the default Ed25519 public key for this keyring
pub fn default_ed25519_pubkey(&self) -> Result<TendermintKey, Error> {
let mut keys = self.ed25519_keys.keys();

if keys.len() == 1 {
Ok(*keys.next().unwrap())
Expand All @@ -77,19 +110,45 @@ impl KeyRing {
}
}

/// Sign a message using the secret key associated with the given public key
/// (if it is in our keyring)
/// Sign a message using the ECDSA secret key associated with the given
/// public key (if it is in our keyring)
pub fn sign_ecdsa(
&self,
public_key: Option<&TendermintKey>,
msg: &[u8],
) -> Result<ecdsa::Signature, Error> {
let signer = match public_key {
Some(public_key) => self.ecdsa_keys.get(public_key).ok_or_else(|| {
format_err!(InvalidKey, "not in keyring: {}", public_key.to_bech32(""))
})?,
None => {
let mut vals = self.ecdsa_keys.values();

if vals.len() > 1 {
fail!(SigningError, "expected only one key in keyring");
} else {
vals.next()
.ok_or_else(|| format_err!(InvalidKey, "keyring is empty"))?
}
}
};

signer.sign(msg)
}

/// Sign a message using the Ed25519 secret key associated with the given
/// public key (if it is in our keyring)
pub fn sign_ed25519(
&self,
public_key: Option<&TendermintKey>,
msg: &[u8],
) -> Result<ed25519::Signature, Error> {
let signer = match public_key {
Some(public_key) => self.keys.get(public_key).ok_or_else(|| {
Some(public_key) => self.ed25519_keys.get(public_key).ok_or_else(|| {
format_err!(InvalidKey, "not in keyring: {}", public_key.to_bech32(""))
})?,
None => {
let mut vals = self.keys.values();
let mut vals = self.ed25519_keys.values();

if vals.len() > 1 {
fail!(SigningError, "expected only one key in keyring");
Expand Down
9 changes: 9 additions & 0 deletions src/keyring/ecdsa.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//! ECDSA (secp256k1) signing keys
pub use signatory::ecdsa::curve::secp256k1::{FixedSignature as Signature, PublicKey, SecretKey};

pub mod signer;
#[cfg(feature = "softsign")]
pub mod softsign;

pub use self::signer::Signer;
57 changes: 57 additions & 0 deletions src/keyring/ecdsa/signer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//! ECDSA (secp256k1) signer
use super::Signature;
use crate::{
error::{Error, ErrorKind::*},
keyring::SigningProvider,
prelude::*,
};
use signatory::signature;
use std::sync::Arc;
use tendermint::TendermintKey;

/// Trait object wrapper for ECDSA signers
#[derive(Clone)]
pub struct Signer {
/// Provider for this signer
provider: SigningProvider,

/// Tendermint public key
public_key: TendermintKey,

/// Signer trait object
signer: Arc<Box<dyn signature::Signer<Signature> + Send + Sync>>,
}

impl Signer {
/// Create a new ECDSA signer
pub fn new(
provider: SigningProvider,
public_key: TendermintKey,
signer: Box<dyn signature::Signer<Signature> + Send + Sync>,
) -> Self {
Self {
provider,
public_key,
signer: Arc::new(signer),
}
}

/// Get the Tendermint public key for this signer
pub fn public_key(&self) -> TendermintKey {
self.public_key
}

/// Get the provider for this signer
pub fn provider(&self) -> SigningProvider {
self.provider
}

/// Sign the given message using this signer
pub fn sign(&self, msg: &[u8]) -> Result<Signature, Error> {
Ok(self
.signer
.try_sign(msg)
.map_err(|e| format_err!(SigningError, "{}", e))?)
}
}
Loading

0 comments on commit 9fccdb2

Please sign in to comment.