Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support secp256k1 consensus key #644

Merged
merged 8 commits into from
May 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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