-
Notifications
You must be signed in to change notification settings - Fork 121
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
feat(softsign): expanded secret key can be used for signing #891
base: main
Are you sure you want to change the base?
Changes from all commits
66e571d
92a6c98
713a8f2
cb0c8c4
c978d47
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
use ed25519_dalek::hazmat::{ExpandedSecretKey, raw_sign}; | ||
use super::{Signature, VerifyingKey}; | ||
use crate::error::{Error, ErrorKind}; | ||
use signature::Signer; | ||
|
@@ -6,70 +7,124 @@ | |
type SigningKeyBytes = [u8; SigningKey::BYTE_SIZE]; | ||
|
||
/// Ed25519 signing key. | ||
#[derive(Clone, Debug)] | ||
pub struct SigningKey(ed25519_consensus::SigningKey); | ||
pub enum SigningKey { | ||
/// Ed25519 signing key. | ||
Ed25519(ed25519_consensus::SigningKey), | ||
/// Ed25519 expanded signing key. | ||
Ed25519Expanded(ExpandedSecretKey), | ||
} | ||
|
||
impl SigningKey { | ||
/// Size of an encoded Ed25519 signing key in bytes. | ||
pub const BYTE_SIZE: usize = 32; | ||
|
||
/// Size of an Ed25519 expanded signing key in bytes. | ||
pub const EXPANDED_BYTE_SIZE: usize = 64; | ||
|
||
/// Size of an Ed25519 public key in bytes. | ||
pub const PUBLIC_KEY_BYTE_SIZE: usize = 32; | ||
|
||
/// Borrow the serialized signing key as bytes. | ||
pub fn as_bytes(&self) -> &SigningKeyBytes { | ||
self.0.as_bytes() | ||
match &self { | ||
SigningKey::Ed25519(signing_key) => signing_key.as_bytes(), | ||
SigningKey::Ed25519Expanded(_) => panic!("unexpected expanded signing key"), | ||
} | ||
} | ||
|
||
/// Get the verifying key for this signing key. | ||
pub fn verifying_key(&self) -> VerifyingKey { | ||
VerifyingKey(self.0.verification_key()) | ||
match &self { | ||
SigningKey::Ed25519(signing_key) => VerifyingKey(signing_key.verification_key()), | ||
SigningKey::Ed25519Expanded(signing_key) => Into::<ed25519_dalek::VerifyingKey>::into(signing_key).as_bytes().as_slice().try_into().unwrap(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a trick so I don't have to reimplement the verifying key for expanded secret keys separately:
The verifying key implementation already falls back to this function so this way I could implement both types of verifying keys in one go. |
||
} | ||
} | ||
} | ||
|
||
impl From<SigningKeyBytes> for SigningKey { | ||
fn from(bytes: SigningKeyBytes) -> Self { | ||
Self(bytes.into()) | ||
SigningKey::Ed25519(bytes.into()) | ||
} | ||
} | ||
|
||
impl From<SigningKey> for ed25519_consensus::SigningKey { | ||
fn from(signing_key: SigningKey) -> ed25519_consensus::SigningKey { | ||
signing_key.0 | ||
match signing_key { | ||
SigningKey::Ed25519(signing_key) => signing_key, | ||
SigningKey::Ed25519Expanded(_) => panic!("unexpected expanded signing key"), | ||
} | ||
} | ||
} | ||
|
||
impl From<&SigningKey> for tendermint_p2p::secret_connection::PublicKey { | ||
fn from(signing_key: &SigningKey) -> tendermint_p2p::secret_connection::PublicKey { | ||
Self::from(&signing_key.0) | ||
match signing_key { | ||
SigningKey::Ed25519(signing_key) => Self::from(signing_key), | ||
SigningKey::Ed25519Expanded(signing_key) => { | ||
let dalek_verifying_key: ed25519_dalek::VerifyingKey = signing_key.into(); | ||
let ed25519_consenus_verification_key: ed25519_consensus::VerificationKey = dalek_verifying_key.as_bytes().as_slice().try_into().unwrap(); | ||
ed25519_consenus_verification_key.into() | ||
}, | ||
} | ||
} | ||
} | ||
|
||
impl From<ed25519_consensus::SigningKey> for SigningKey { | ||
fn from(signing_key: ed25519_consensus::SigningKey) -> SigningKey { | ||
SigningKey(signing_key) | ||
SigningKey::Ed25519(signing_key) | ||
} | ||
} | ||
|
||
impl From<tendermint::private_key::Ed25519> for SigningKey { | ||
fn from(signing_key: tendermint::private_key::Ed25519) -> SigningKey { | ||
signing_key | ||
SigningKey::Ed25519(signing_key | ||
.as_bytes() | ||
.try_into() | ||
.expect("invalid Ed25519 signing key") | ||
.expect("invalid Ed25519 signing key")) | ||
} | ||
} | ||
|
||
impl Signer<Signature> for SigningKey { | ||
fn try_sign(&self, msg: &[u8]) -> signature::Result<Signature> { | ||
Ok(self.0.sign(msg).to_bytes().into()) | ||
match self { | ||
SigningKey::Ed25519(signing_key) => Ok(signing_key.sign(msg).to_bytes().into()), | ||
SigningKey::Ed25519Expanded(signing_key) => Ok(raw_sign::<sha2::Sha512>(signing_key, msg, &signing_key.into())), | ||
} | ||
} | ||
} | ||
|
||
impl TryFrom<&[u8]> for SigningKey { | ||
type Error = Error; | ||
|
||
fn try_from(slice: &[u8]) -> Result<Self, Error> { | ||
slice | ||
.try_into() | ||
.map(Self) | ||
.map_err(|_| ErrorKind::InvalidKey.into()) | ||
if slice.len() == SigningKey::BYTE_SIZE { | ||
// Assume seed key. | ||
slice | ||
.try_into() | ||
.map(|e| SigningKey::Ed25519(e)) | ||
.map_err(|_| ErrorKind::InvalidKey.into() | ||
) | ||
} else if slice.len() == SigningKey::EXPANDED_BYTE_SIZE { | ||
// Assume key is big-endian encoded expanded secret key. (SHA512 hashed seed key.) | ||
slice[0..SigningKey::EXPANDED_BYTE_SIZE] | ||
.try_into() | ||
.map(|e| SigningKey::Ed25519Expanded(e)) | ||
.map_err(|_| ErrorKind::InvalidKey.into() | ||
) | ||
} else if slice.len() == (SigningKey::EXPANDED_BYTE_SIZE + SigningKey::PUBLIC_KEY_BYTE_SIZE) { | ||
// Assume key is little-endian encoded expanded secret key. (Exported from YubiHSM.) | ||
let mut slice_copy: [u8; SigningKey::EXPANDED_BYTE_SIZE | ||
+ SigningKey::PUBLIC_KEY_BYTE_SIZE] = | ||
[0; SigningKey::EXPANDED_BYTE_SIZE + SigningKey::PUBLIC_KEY_BYTE_SIZE]; | ||
slice_copy.copy_from_slice(slice); | ||
slice_copy[0..32].reverse(); | ||
slice_copy[0..SigningKey::EXPANDED_BYTE_SIZE] | ||
.try_into() | ||
.map(|e| SigningKey::Ed25519Expanded(e)) | ||
.map_err(|_| ErrorKind::InvalidKey.into() | ||
) | ||
} else { | ||
Err(ErrorKind::InvalidKey.context(format!("invalid Ed25519 key size {}", slice.len())).into()) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of having two signing codepaths that duplicate each other and use different dependencies to accomplish the same thing depending on whether or not the key has been pre-expanded, I think this PR would be a lot less messy if it moved (back) to
ed25519-dalek
wholesale, since apparently there is no path forward on supportingExpandedSecretKey
-like functionality ined25519-consensus
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My preference would still be to unify these somehow. You should be able to initialize an
ExpandedSecretKey
from either a seed or its SHA-512 expansion.Then this could just be: