Skip to content

Commit

Permalink
Support secp256k1 consensus key (#644)
Browse files Browse the repository at this point in the history
  • Loading branch information
mkaczanowski authored May 30, 2023
1 parent ed24642 commit b6ce617
Show file tree
Hide file tree
Showing 15 changed files with 264 additions and 89 deletions.
16 changes: 11 additions & 5 deletions Cargo.lock

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

10 changes: 5 additions & 5 deletions src/amino_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
#![allow(missing_docs)]

pub mod block_id;
pub mod ed25519;
pub mod message;
pub mod ping;
pub mod proposal;
pub mod pubkey;
pub mod remote_error;
pub mod signature;
pub mod time;
Expand All @@ -17,15 +17,15 @@ pub mod vote;

pub use self::{
block_id::{BlockId, CanonicalBlockId, CanonicalPartSetHeader, PartsSetHeader},
ed25519::{
PubKeyRequest, PubKeyResponse, AMINO_NAME as PUBKEY_AMINO_NAME,
AMINO_PREFIX as PUBKEY_PREFIX,
},
ping::{PingRequest, PingResponse, AMINO_NAME as PING_AMINO_NAME, AMINO_PREFIX as PING_PREFIX},
proposal::{
Proposal, SignProposalRequest, SignedProposalResponse, AMINO_NAME as PROPOSAL_AMINO_NAME,
AMINO_PREFIX as PROPOSAL_PREFIX,
},
pubkey::{
PubKeyRequest, PubKeyResponse, AMINO_NAME as PUBKEY_AMINO_NAME,
AMINO_PREFIX as PUBKEY_PREFIX,
},
remote_error::RemoteError,
signature::{SignableMsg, SignedMsgType},
time::TimeMsg,
Expand Down
5 changes: 2 additions & 3 deletions src/amino_types/proposal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ use super::{
validate::{self, ConsensusMessage, Error::*},
ParseChainId, TendermintRequest,
};
use crate::{config::validator::ProtocolVersion, rpc};
use crate::{config::validator::ProtocolVersion, keyring::signature::Signature, rpc};
use bytes::BufMut;
use bytes_v0_5::BytesMut as BytesMutV05;
use ed25519_dalek as ed25519;
use once_cell::sync::Lazy;
use prost::Message as _;
use prost_amino::{EncodeError, Message};
Expand Down Expand Up @@ -165,7 +164,7 @@ impl SignableMsg for SignProposalRequest {

Ok(true)
}
fn set_signature(&mut self, sig: &ed25519::Signature) {
fn set_signature(&mut self, sig: &Signature) {
if let Some(ref mut prop) = self.proposal {
prop.signature = sig.as_ref().to_vec();
}
Expand Down
22 changes: 18 additions & 4 deletions src/amino_types/ed25519.rs → src/amino_types/pubkey.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
use super::compute_prefix;
use once_cell::sync::Lazy;
use prost_amino_derive::Message;
use tendermint::public_key::{Ed25519, PublicKey};
use tendermint::public_key::{Ed25519, PublicKey, Secp256k1};

// Note:On the golang side this is generic in the sense that it could everything that implements
// github.com/tendermint/tendermint/crypto.PubKey
// While this is meant to be used with different key-types, it currently only uses a PubKeyEd25519
// version.
// TODO(ismail): make this more generic (by modifying prost and adding a trait for PubKey)

pub const AMINO_NAME: &str = "tendermint/remotesigner/PubKeyRequest";
Expand All @@ -17,6 +15,9 @@ pub static AMINO_PREFIX: Lazy<Vec<u8>> = Lazy::new(|| compute_prefix(AMINO_NAME)
pub struct PubKeyResponse {
#[prost_amino(bytes, tag = "1", amino_name = "tendermint/PubKeyEd25519")]
pub pub_key_ed25519: Vec<u8>,

#[prost_amino(bytes, tag = "2", amino_name = "tendermint/PubKeySecp256k1")]
pub pub_key_secp256k1: Vec<u8>,
}

#[derive(Clone, Eq, PartialEq, Message)]
Expand All @@ -29,7 +30,11 @@ impl TryFrom<PubKeyResponse> for PublicKey {
// This does not check if the underlying pub_key_ed25519 has the right size.
// The caller needs to make sure that this is actually the case.
fn try_from(response: PubKeyResponse) -> eyre::Result<PublicKey> {
Ok(Ed25519::from_bytes(&response.pub_key_ed25519)?.into())
if !response.pub_key_ed25519.is_empty() {
Ok(Ed25519::from_bytes(&response.pub_key_ed25519)?.into())
} else {
Ok(Secp256k1::from_sec1_bytes(&response.pub_key_secp256k1)?.into())
}
}
}

Expand All @@ -38,6 +43,12 @@ impl From<PublicKey> for PubKeyResponse {
if let PublicKey::Ed25519(ref pk) = public_key {
PubKeyResponse {
pub_key_ed25519: pk.as_bytes().to_vec(),
pub_key_secp256k1: vec![],
}
} else if let PublicKey::Secp256k1(ref pk) = public_key {
PubKeyResponse {
pub_key_ed25519: vec![],
pub_key_secp256k1: pk.to_bytes().to_vec(),
}
} else {
unimplemented!(
Expand Down Expand Up @@ -129,6 +140,7 @@ mod tests {
0xe7, 0xc1, 0xd4, 0x69, 0xc3, 0x44, 0x26, 0xec, 0xef, 0xc0, 0x72, 0xa, 0x52, 0x4d,
0x37, 0x32, 0xef, 0xed,
],
pub_key_secp256k1: vec![],
};
let mut got = vec![];
let _have = msg.encode(&mut got);
Expand All @@ -155,6 +167,7 @@ mod tests {
0x76, 0x55, 0x2b, 0x2e, 0x8d, 0x19, 0x6f, 0xe9, 0x12, 0x14, 0x50, 0x80, 0x6b, 0xd0,
0xd9, 0x3f, 0xd0, 0xcb,
],
pub_key_secp256k1: vec![],
};
let orig = pk.clone();
let got: PublicKey = pk.try_into().unwrap();
Expand All @@ -171,6 +184,7 @@ mod tests {
fn test_empty_into() {
let empty_msg = PubKeyResponse {
pub_key_ed25519: vec![],
pub_key_secp256k1: vec![],
};
// we expect this to panic:
let _got: PublicKey = empty_msg.try_into().unwrap();
Expand Down
6 changes: 3 additions & 3 deletions src/amino_types/signature.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::validate;
use crate::config::validator::ProtocolVersion;
use crate::keyring::signature::Signature;
use bytes::BufMut;
use ed25519_dalek as ed25519;
use prost_amino::{DecodeError, EncodeError};
use tendermint::{chain, consensus};

Expand All @@ -15,8 +15,8 @@ pub trait SignableMsg {
sign_bytes: &mut B,
) -> Result<bool, EncodeError>;

/// Set the Ed25519 signature on the underlying message
fn set_signature(&mut self, sig: &ed25519::Signature);
/// Set the signature on the underlying message
fn set_signature(&mut self, sig: &Signature);
fn validate(&self) -> Result<(), validate::Error>;
fn consensus_state(&self) -> Option<consensus::State>;
fn height(&self) -> Option<i64>;
Expand Down
5 changes: 2 additions & 3 deletions src/amino_types/vote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ use super::{
validate::{self, ConsensusMessage, Error::*},
ParseChainId, SignedMsgType, TendermintRequest,
};
use crate::{config::validator::ProtocolVersion, rpc};
use crate::{config::validator::ProtocolVersion, keyring::signature::Signature, rpc};
use bytes::BufMut;
use bytes_v0_5::BytesMut as BytesMutV05;
use ed25519_dalek as ed25519;
use once_cell::sync::Lazy;
use prost::Message as _;
use prost_amino::{error::EncodeError, Message};
Expand Down Expand Up @@ -227,7 +226,7 @@ impl SignableMsg for SignVoteRequest {

Ok(true)
}
fn set_signature(&mut self, sig: &ed25519::Signature) {
fn set_signature(&mut self, sig: &Signature) {
if let Some(ref mut vt) = self.vote {
vt.signature = sig.as_ref().to_vec();
}
Expand Down
2 changes: 1 addition & 1 deletion src/commands/ledger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ impl Runnable for InitCommand {
)
.unwrap();

let _sig = chain.keyring.sign_ed25519(None, &to_sign).unwrap();
let _sig = chain.keyring.sign(None, &to_sign).unwrap();

println!(
"Successfully called the init command with height {}, and round {}",
Expand Down
15 changes: 15 additions & 0 deletions src/key_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use std::{

use ed25519_dalek as ed25519;
use ed25519_dalek::SECRET_KEY_LENGTH;
use k256::ecdsa;
use rand_core::{OsRng, RngCore};
use subtle_encoding::base64;
use zeroize::Zeroizing;
Expand Down Expand Up @@ -57,6 +58,20 @@ pub fn load_base64_ed25519_key(path: impl AsRef<Path>) -> Result<ed25519::Keypai
Ok(ed25519::Keypair { secret, public })
}

/// Load a Base64-encoded Secp256k1 secret key
pub fn load_base64_secp256k1_key(
path: impl AsRef<Path>,
) -> Result<(ecdsa::SigningKey, ecdsa::VerifyingKey), Error> {
let key_bytes = load_base64_secret(path)?;

let signing = ecdsa::SigningKey::from_bytes(&key_bytes)
.map_err(|e| format_err!(InvalidKey, "invalid ECDSA key: {}", e))?;

let veryfing = ecdsa::VerifyingKey::from(&signing);

Ok((signing, veryfing))
}

/// Store Base64-encoded secret data at the given path
pub fn write_base64_secret(path: impl AsRef<Path>, data: &[u8]) -> Result<(), Error> {
let base64_data = Zeroizing::new(base64::encode(data));
Expand Down
81 changes: 53 additions & 28 deletions src/keyring.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
//! Signing keyring. Presently specialized for Ed25519.
//! Signing keyring. Presently specialized for Ed25519 and ECDSA.

pub mod ecdsa;
pub mod ed25519;
pub mod format;
pub mod providers;
pub mod signature;

pub use self::{format::Format, providers::SigningProvider};
pub use self::{format::Format, providers::SigningProvider, signature::Signature};
use crate::{
chain,
config::provider::ProviderConfig,
Expand Down Expand Up @@ -105,13 +106,25 @@ impl KeyRing {
}

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

if keys.len() == 1 {
Ok(*keys.next().unwrap())
} else {
fail!(InvalidKey, "expected only one ed25519 key in keyring");
}
} else if !self.ecdsa_keys.is_empty() {
let mut keys = self.ecdsa_keys.keys();

if keys.len() == 1 {
Ok(*keys.next().unwrap())
if keys.len() == 1 {
Ok(*keys.next().unwrap())
} else {
fail!(InvalidKey, "expected only one ecdsa key in keyring");
}
} else {
fail!(InvalidKey, "expected only one key in keyring");
fail!(InvalidKey, "keyring is empty");
}
}

Expand Down Expand Up @@ -151,28 +164,40 @@ impl KeyRing {

/// Sign a message using the 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.ed25519_keys.get(public_key).ok_or_else(|| {
format_err!(InvalidKey, "not in keyring: {}", public_key.to_bech32(""))
})?,
None => {
let mut vals = self.ed25519_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"))?
}
}
};
pub fn sign(&self, public_key: Option<&TendermintKey>, msg: &[u8]) -> Result<Signature, Error> {
if self.ed25519_keys.len() > 1 || self.ecdsa_keys.len() > 1 {
fail!(SigningError, "expected only one key in keyring");
}

signer.sign(msg)
if !self.ed25519_keys.is_empty() {
let signer = match public_key {
Some(public_key) => self.ed25519_keys.get(public_key).ok_or_else(|| {
format_err!(InvalidKey, "not in keyring: {}", public_key.to_bech32(""))
}),
None => self
.ed25519_keys
.values()
.next()
.ok_or_else(|| format_err!(InvalidKey, "ed25519 keyring is empty")),
}?;

Ok(Signature::Ed25519(signer.sign(msg)?))
} else if !self.ecdsa_keys.is_empty() {
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 => self
.ecdsa_keys
.values()
.next()
.ok_or_else(|| format_err!(InvalidKey, "ecdsa keyring is empty")),
}?;

Ok(Signature::Ecdsa(signer.sign(msg)?))
} else {
Err(format_err!(InvalidKey, "keyring is empty").into())
}
}
}

Expand Down
22 changes: 22 additions & 0 deletions src/keyring/signature.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//! Signing signature

pub use ed25519_dalek as ed25519;
pub use k256::ecdsa;

/// Cryptographic signature used for block signing
pub enum Signature {
/// ED25519 signature
Ed25519(ed25519::Signature),

/// ECDSA signagure (e.g secp256k1)
Ecdsa(ecdsa::Signature),
}

impl AsRef<[u8]> for Signature {
fn as_ref(&self) -> &[u8] {
match &self {
Signature::Ed25519(sig) => sig.as_ref(),
Signature::Ecdsa(sig) => sig.as_ref(),
}
}
}
Loading

0 comments on commit b6ce617

Please sign in to comment.