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

feat(softsign): expanded secret key can be used for signing #891

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ uuid = { version = "1", features = ["serde"], optional = true }
wait-timeout = "0.2"
yubihsm = { version = "0.42", features = ["secp256k1", "setup", "usb"], optional = true }
zeroize = "1"
ed25519-dalek = { version = "2.1.1", features = ["hazmat"] }

[dev-dependencies]
abscissa_core = { version = "0.7", features = ["testing"] }
Expand Down
85 changes: 70 additions & 15 deletions src/keyring/ed25519/signing_key.rs
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;
Expand All @@ -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 {

Check failure on line 10 in src/keyring/ed25519/signing_key.rs

View workflow job for this annotation

GitHub Actions / Clippy

large size difference between variants
/// Ed25519 signing key.
Ed25519(ed25519_consensus::SigningKey),
/// Ed25519 expanded signing key.
Ed25519Expanded(ExpandedSecretKey),
}
Comment on lines +10 to +15
Copy link
Member

@tony-iqlusion tony-iqlusion Apr 5, 2024

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 supporting ExpandedSecretKey-like functionality in ed25519-consensus.

Copy link
Member

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:

Suggested change
pub enum SigningKey {
/// Ed25519 signing key.
Ed25519(ed25519_consensus::SigningKey),
/// Ed25519 expanded signing key.
Ed25519Expanded(ExpandedSecretKey),
}
pub struct SigningKey(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(),
Copy link
Author

Choose a reason for hiding this comment

The 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:

  1. Convert the ExpandedSecretKey into ed25519-dalek's VerifyingKey (trait implemented in ed25519-dalek)
  2. Get the bytes from the VerifyingKey, turn them into a slice
  3. Use the trait defined in ed25519-consensus to turn a slice of bytes into an ed25519-consensus VerifyingKey.

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))

Check failure on line 104 in src/keyring/ed25519/signing_key.rs

View workflow job for this annotation

GitHub Actions / Clippy

redundant closure
.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))

Check failure on line 111 in src/keyring/ed25519/signing_key.rs

View workflow job for this annotation

GitHub Actions / Clippy

redundant closure
.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))

Check failure on line 123 in src/keyring/ed25519/signing_key.rs

View workflow job for this annotation

GitHub Actions / Clippy

redundant closure
.map_err(|_| ErrorKind::InvalidKey.into()
)
} else {
Err(ErrorKind::InvalidKey.context(format!("invalid Ed25519 key size {}", slice.len())).into())
}
}
}
Loading