diff --git a/Cargo.lock b/Cargo.lock index a8bf0ee06e..cce2c78ebc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -510,7 +510,6 @@ dependencies = [ "astria-sequencer-client", "clap", "color-eyre", - "ed25519-consensus", "hex", "rand 0.8.5", "serde", @@ -535,7 +534,6 @@ dependencies = [ "astria-test-utils", "async-trait", "axum", - "ed25519-consensus", "ethers", "futures", "hex", @@ -587,7 +585,6 @@ dependencies = [ "celestia-tendermint", "celestia-types", "chrono", - "ed25519-consensus", "futures", "futures-bounded", "hex", @@ -738,7 +735,6 @@ dependencies = [ "bytes", "cnidarium", "cnidarium-component", - "ed25519-consensus", "futures", "hex", "ibc-proto", @@ -773,7 +769,6 @@ version = "0.1.0" dependencies = [ "astria-core", "async-trait", - "ed25519-consensus", "futures", "futures-util", "hex", diff --git a/Cargo.toml b/Cargo.toml index 8d389b412c..7990328916 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,9 +55,6 @@ bytes = "1" celestia-tendermint = "0.32.1" celestia-types = "0.1.1" clap = "4.5.4" -ed25519-consensus = { version = "2.1.0", default-features = false, features = [ - "std", -] } ethers = "2.0.11" futures = "0.3" hex = "0.4" diff --git a/crates/astria-cli/Cargo.toml b/crates/astria-cli/Cargo.toml index 570f15c1ba..0e2690babc 100644 --- a/crates/astria-cli/Cargo.toml +++ b/crates/astria-cli/Cargo.toml @@ -17,7 +17,6 @@ color-eyre = "0.6" astria-core = { path = "../astria-core" } clap = { workspace = true, features = ["derive", "env"] } -ed25519-consensus = { workspace = true } hex = { workspace = true } rand = { workspace = true } serde = { workspace = true, features = ["derive"] } diff --git a/crates/astria-cli/src/commands/sequencer.rs b/crates/astria-cli/src/commands/sequencer.rs index 4534dee5f0..842091f304 100644 --- a/crates/astria-cli/src/commands/sequencer.rs +++ b/crates/astria-cli/src/commands/sequencer.rs @@ -19,7 +19,6 @@ use astria_core::{ use astria_sequencer_client::{ tendermint, tendermint_rpc::endpoint, - Address, Client, HttpClient, SequencerClientExt, @@ -32,7 +31,6 @@ use color_eyre::{ Context, }, }; -use ed25519_consensus::VerificationKeyBytes; use rand::rngs::OsRng; use crate::cli::sequencer::{ @@ -67,7 +65,7 @@ fn get_private_key_pretty(signing_key: &SigningKey) -> String { /// Get the address from the signing key fn get_address_pretty(signing_key: &SigningKey) -> String { - let address = Address::from_verification_key(signing_key.verification_key()); + let address = *signing_key.verification_key().address(); hex::encode(address.to_vec()) } @@ -461,9 +459,7 @@ pub(crate) async fn sudo_address_change(args: &SudoAddressChangeArgs) -> eyre::R pub(crate) async fn validator_update(args: &ValidatorUpdateArgs) -> eyre::Result<()> { let public_key_raw = hex::decode(args.validator_public_key.as_str()) .wrap_err("failed to decode public key into bytes")?; - let public_key_bytes = VerificationKeyBytes::try_from(public_key_raw.as_slice()) - .wrap_err("public key bytes were too long, must be 32 bytes")?; - let pub_key = tendermint::PublicKey::from_raw_ed25519(public_key_bytes.as_bytes()) + let pub_key = tendermint::PublicKey::from_raw_ed25519(&public_key_raw) .expect("failed to parse public key from parsed bytes"); let validator_update = tendermint::validator::Update { pub_key, @@ -500,7 +496,7 @@ async fn submit_transaction( .map_err(|_| eyre!("invalid private key length; must be 32 bytes"))?; let sequencer_key = SigningKey::from(private_key_bytes); - let from_address = Address::from_verification_key(sequencer_key.verification_key()); + let from_address = *sequencer_key.verification_key().address(); let nonce_res = sequencer_client .get_latest_nonce(from_address) diff --git a/crates/astria-composer/Cargo.toml b/crates/astria-composer/Cargo.toml index 68555171a4..c6fa65eb7b 100644 --- a/crates/astria-composer/Cargo.toml +++ b/crates/astria-composer/Cargo.toml @@ -25,7 +25,6 @@ tonic-health = "0.10.2" async-trait = { workspace = true } axum = { workspace = true } -ed25519-consensus = { workspace = true } ethers = { workspace = true, features = ["ws"] } futures = { workspace = true } humantime = { workspace = true } diff --git a/crates/astria-composer/src/executor/builder.rs b/crates/astria-composer/src/executor/builder.rs index 973bc1302b..081de7993b 100644 --- a/crates/astria-composer/src/executor/builder.rs +++ b/crates/astria-composer/src/executor/builder.rs @@ -6,7 +6,6 @@ use std::{ use astria_core::{ crypto::SigningKey, - primitive::v1::Address, protocol::transaction::v1alpha1::action::SequenceAction, }; use astria_eyre::eyre::{ @@ -51,7 +50,7 @@ impl Builder { format!("failed reading signing key from file at path `{private_key_file}`") })?; - let sequencer_address = Address::from_verification_key(sequencer_key.verification_key()); + let sequencer_address = *sequencer_key.verification_key().address(); let (serialized_rollup_transaction_tx, serialized_rollup_transaction_rx) = tokio::sync::mpsc::channel::(256); diff --git a/crates/astria-conductor/Cargo.toml b/crates/astria-conductor/Cargo.toml index 44a2e3c10d..4fa64f1f93 100644 --- a/crates/astria-conductor/Cargo.toml +++ b/crates/astria-conductor/Cargo.toml @@ -31,7 +31,6 @@ telemetry = { package = "astria-telemetry", path = "../astria-telemetry", featur base64 = { workspace = true } bytes = { workspace = true } -ed25519-consensus = { workspace = true } futures = { workspace = true } hex = { workspace = true } humantime = { workspace = true } diff --git a/crates/astria-conductor/src/celestia/block_verifier.rs b/crates/astria-conductor/src/celestia/block_verifier.rs index b945612f96..9e7770e17d 100644 --- a/crates/astria-conductor/src/celestia/block_verifier.rs +++ b/crates/astria-conductor/src/celestia/block_verifier.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use ed25519_consensus::{ +use astria_core::crypto::{ Signature, VerificationKey, }; @@ -48,7 +48,7 @@ pub(super) enum QuorumError { #[error( "failed to recreate signature for validator from validator set to verify vote signature" )] - Signature(#[source] ed25519_consensus::Error), + Signature(#[source] astria_core::crypto::Error), #[error("total voting power overflowed u64")] TotalVotingPowerOverflowed, @@ -65,10 +65,10 @@ pub(super) enum QuorumError { #[error( "failed to recreate public key for validator from validator set to verify vote signature" )] - VerificationKey(#[source] ed25519_consensus::Error), + VerificationKey(#[source] astria_core::crypto::Error), #[error("failed to verify vote signature")] - VerifyVoteSignature(#[source] ed25519_consensus::Error), + VerifyVoteSignature(#[source] astria_core::crypto::Error), } /// This function ensures that the given Commit has quorum, ie that the Commit contains >2/3 voting @@ -305,7 +305,7 @@ mod test { signatures: vec![tendermint::block::CommitSig::BlockIdFlagCommit { validator_address: address, timestamp, - signature: Some(signature.into()), + signature: Some(signature.to_bytes().as_ref().try_into().unwrap()), }], ..Default::default() }; diff --git a/crates/astria-conductor/tests/blackbox/helpers/mod.rs b/crates/astria-conductor/tests/blackbox/helpers/mod.rs index e53491c67c..1f43ce7ab7 100644 --- a/crates/astria-conductor/tests/blackbox/helpers/mod.rs +++ b/crates/astria-conductor/tests/blackbox/helpers/mod.rs @@ -587,7 +587,7 @@ pub fn make_commit(height: u32) -> tendermint::block::Commit { signatures: vec![tendermint::block::CommitSig::BlockIdFlagCommit { validator_address: validator.address, timestamp, - signature: Some(signature.into()), + signature: Some(signature.to_bytes().as_ref().try_into().unwrap()), }], } } diff --git a/crates/astria-core/Cargo.toml b/crates/astria-core/Cargo.toml index 3a416eeeb3..7d273b985e 100644 --- a/crates/astria-core/Cargo.toml +++ b/crates/astria-core/Cargo.toml @@ -24,7 +24,9 @@ merkle = { package = "astria-merkle", path = "../astria-merkle" } bytes = { workspace = true } celestia-tendermint = { workspace = true } -ed25519-consensus = { workspace = true } +ed25519-consensus = { version = "2.1.0", default-features = false, features = [ + "std", +] } ibc-types = { workspace = true } indexmap = { workspace = true } pbjson-types = { workspace = true } diff --git a/crates/astria-core/src/crypto.rs b/crates/astria-core/src/crypto.rs index 556fe68c21..2c1b6a98e6 100644 --- a/crates/astria-core/src/crypto.rs +++ b/crates/astria-core/src/crypto.rs @@ -1,23 +1,47 @@ -use std::fmt::{ - self, - Debug, - Formatter, +use std::{ + cmp::Ordering, + fmt::{ + self, + Debug, + Display, + Formatter, + }, + hash::{ + Hash, + Hasher, + }, + sync::OnceLock, }; +use base64::{ + display::Base64Display, + prelude::BASE64_STANDARD, + Engine, +}; use ed25519_consensus::{ - Signature, + Error as Ed25519Error, + Signature as Ed25519Signature, SigningKey as Ed25519SigningKey, - VerificationKey, + VerificationKey as Ed25519VerificationKey, }; use rand::{ CryptoRng, RngCore, }; +use sha2::{ + Digest as _, + Sha256, +}; use zeroize::{ Zeroize, ZeroizeOnDrop, }; +use crate::primitive::v1::{ + Address, + ADDRESS_LEN, +}; + /// An Ed25519 signing key. // *Implementation note*: this is currently a refinement type around // ed25519_consensus::SigningKey overriding its Debug implementation @@ -34,7 +58,7 @@ impl SigningKey { /// Creates a signature on `msg` using this key. #[must_use] pub fn sign(&self, msg: &[u8]) -> Signature { - self.0.sign(msg) + Signature(self.0.sign(msg)) } /// Returns the byte encoding of the signing key. @@ -52,7 +76,10 @@ impl SigningKey { /// Returns the verification key associated with this signing key. #[must_use] pub fn verification_key(&self) -> VerificationKey { - self.0.verification_key() + VerificationKey { + key: self.0.verification_key(), + address: OnceLock::new(), + } } } @@ -60,7 +87,7 @@ impl Debug for SigningKey { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { formatter .debug_struct("SigningKey") - .field("verification_key", &self.0.verification_key()) + .field("verification_key", &self.verification_key()) .finish_non_exhaustive() // avoids printing secret fields } } @@ -84,3 +111,328 @@ impl From<[u8; 32]> for SigningKey { Self(Ed25519SigningKey::from(seed)) } } + +/// An Ed25519 verification key. +#[derive(Clone)] +pub struct VerificationKey { + key: Ed25519VerificationKey, + // The address is lazily-initialized. Since it may or may not be initialized for any given + // instance of a verification key, it is excluded from `PartialEq`, `Eq`, `PartialOrd`, `Ord` + // and `Hash` impls. + address: OnceLock
, +} + +impl VerificationKey { + /// Returns the byte encoding of the verification key. + pub fn to_bytes(&self) -> [u8; 32] { + self.key.to_bytes() + } + + /// Returns the byte encoding of the verification key. + pub fn as_bytes(&self) -> &[u8; 32] { + self.key.as_bytes() + } + + /// Verifies `signature` on the given `msg`. + /// + /// # Errors + /// Returns an error if verification fails. + pub fn verify(&self, signature: &Signature, msg: &[u8]) -> Result<(), Error> { + self.key.verify(&signature.0, msg).map_err(Error) + } + + /// Returns the sequencer address of this verification key. + /// + /// The address is the first 20 bytes of the sha256 hash of the verification key. + // Silence the clippy lint because the function body asserts that the panic + // cannot happen. + #[allow(clippy::missing_panics_doc)] + pub fn address(&self) -> &Address { + self.address.get_or_init(|| { + /// this ensures that `ADDRESS_LEN` is never accidentally changed to a value + /// that would violate this assumption. + #[allow(clippy::assertions_on_constants)] + const _: () = assert!(ADDRESS_LEN <= 32); + let bytes: [u8; 32] = Sha256::digest(self).into(); + Address::try_from_slice(&bytes[..ADDRESS_LEN]) + .expect("can convert 32 byte hash to 20 byte array") + }) + } +} + +impl Debug for VerificationKey { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + let mut debug_struct = formatter.debug_struct("VerifyingKey"); + debug_struct.field("key", &BASE64_STANDARD.encode(self.key.as_ref())); + if let Some(address) = self.address.get() { + debug_struct.field("address", address); + } else { + debug_struct.field("address", &"unset"); + } + debug_struct.finish() + } +} + +impl Display for VerificationKey { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + Base64Display::new(self.key.as_ref(), &BASE64_STANDARD).fmt(formatter) + } +} + +impl PartialEq for VerificationKey { + fn eq(&self, other: &Self) -> bool { + self.key.eq(&other.key) + } +} + +impl Eq for VerificationKey {} + +impl PartialOrd for VerificationKey { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for VerificationKey { + fn cmp(&self, other: &Self) -> Ordering { + self.key.cmp(&other.key) + } +} + +impl Hash for VerificationKey { + fn hash(&self, hasher: &mut H) { + self.key.hash(hasher); + } +} + +impl AsRef<[u8]> for VerificationKey { + fn as_ref(&self) -> &[u8] { + self.key.as_ref() + } +} + +impl TryFrom<&[u8]> for VerificationKey { + type Error = Error; + + fn try_from(slice: &[u8]) -> Result { + let key = Ed25519VerificationKey::try_from(slice)?; + Ok(Self { + key, + address: OnceLock::new(), + }) + } +} + +impl TryFrom<[u8; 32]> for VerificationKey { + type Error = Error; + + fn try_from(bytes: [u8; 32]) -> Result { + let key = Ed25519VerificationKey::try_from(bytes)?; + Ok(Self { + key, + address: OnceLock::new(), + }) + } +} + +/// An Ed25519 signature. +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct Signature(Ed25519Signature); + +impl Signature { + /// Returns the bytes of the signature. + #[must_use] + pub fn to_bytes(&self) -> [u8; 64] { + self.0.to_bytes() + } +} + +impl Debug for Signature { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + (&self.0 as &dyn Debug).fmt(formatter) + } +} + +impl Display for Signature { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + Base64Display::new(&self.0.to_bytes(), &BASE64_STANDARD).fmt(formatter) + } +} + +impl From<[u8; 64]> for Signature { + fn from(bytes: [u8; 64]) -> Self { + Self(Ed25519Signature::from(bytes)) + } +} + +impl TryFrom<&[u8]> for Signature { + type Error = Error; + + fn try_from(slice: &[u8]) -> Result { + let signature = Ed25519Signature::try_from(slice)?; + Ok(Self(signature)) + } +} + +/// An error related to Ed25519 signing. +#[derive(Copy, Clone, Eq, PartialEq, thiserror::Error, Debug)] +#[error(transparent)] +pub struct Error(#[from] Ed25519Error); + +#[cfg(test)] +mod tests { + use super::*; + + // From https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html + #[test] + // allow: we want explicit assertions here to match the documented expected behavior. + #[allow(clippy::nonminimal_bool)] + fn verification_key_comparisons_should_be_consistent() { + // A key which compares greater than "low" ones below, and with its address uninitialized. + let high_uninit = VerificationKey { + key: SigningKey::from([255; 32]).0.verification_key(), + address: OnceLock::new(), + }; + // A key equal to `high_uninit`, but with its address initialized. + let high_init = VerificationKey { + key: high_uninit.key, + address: OnceLock::from(Address::from([255; 20])), + }; + // A key which compares less than "high" ones above, and with its address uninitialized. + let low_uninit = VerificationKey { + key: SigningKey::from([0; 32]).0.verification_key(), + address: OnceLock::new(), + }; + // A key equal to `low_uninit`, but with its address initialized. + let low_init = VerificationKey { + key: low_uninit.key, + address: OnceLock::from(Address::from([255; 20])), + }; + + assert!(high_uninit.cmp(&high_uninit) == Ordering::Equal); + assert!(high_uninit.cmp(&high_init) == Ordering::Equal); + assert!(high_init.cmp(&high_uninit) == Ordering::Equal); + assert!(high_init.cmp(&high_init) == Ordering::Equal); + assert!(high_uninit.cmp(&low_uninit) == Ordering::Greater); + assert!(high_uninit.cmp(&low_init) == Ordering::Greater); + assert!(high_init.cmp(&low_uninit) == Ordering::Greater); + assert!(high_init.cmp(&low_init) == Ordering::Greater); + assert!(low_uninit.cmp(&high_uninit) == Ordering::Less); + assert!(low_uninit.cmp(&high_init) == Ordering::Less); + assert!(low_init.cmp(&high_uninit) == Ordering::Less); + assert!(low_init.cmp(&high_init) == Ordering::Less); + + // 1. a == b if and only if partial_cmp(a, b) == Some(Equal) + assert!(high_uninit == high_uninit); // Some(Equal) + assert!(high_uninit == high_init); // Some(Equal) + assert!(high_init == high_uninit); // Some(Equal) + assert!(high_init == high_init); // Some(Equal) + assert!(!(high_uninit == low_uninit)); // Some(Greater) + assert!(!(high_uninit == low_init)); // Some(Greater) + assert!(!(high_init == low_uninit)); // Some(Greater) + assert!(!(high_init == low_init)); // Some(Greater) + assert!(!(low_uninit == high_uninit)); // Some(Less) + assert!(!(low_uninit == high_init)); // Some(Less) + assert!(!(low_init == high_uninit)); // Some(Less) + assert!(!(low_init == high_init)); // Some(Less) + + // 2. a < b if and only if partial_cmp(a, b) == Some(Less) + assert!(low_uninit < high_uninit); // Some(Less) + assert!(low_uninit < high_init); // Some(Less) + assert!(low_init < high_uninit); // Some(Less) + assert!(low_init < high_init); // Some(Less) + assert!(!(high_uninit < high_uninit)); // Some(Equal) + assert!(!(high_uninit < high_init)); // Some(Equal) + assert!(!(high_init < high_uninit)); // Some(Equal) + assert!(!(high_init < high_init)); // Some(Equal) + assert!(!(high_uninit < low_uninit)); // Some(Greater) + assert!(!(high_uninit < low_init)); // Some(Greater) + assert!(!(high_init < low_uninit)); // Some(Greater) + assert!(!(high_init < low_init)); // Some(Greater) + + // 3. a > b if and only if partial_cmp(a, b) == Some(Greater) + assert!(high_uninit > low_uninit); // Some(Greater) + assert!(high_uninit > low_init); // Some(Greater) + assert!(high_init > low_uninit); // Some(Greater) + assert!(high_init > low_init); // Some(Greater) + assert!(!(high_uninit > high_uninit)); // Some(Equal) + assert!(!(high_uninit > high_init)); // Some(Equal) + assert!(!(high_init > high_uninit)); // Some(Equal) + assert!(!(high_init > high_init)); // Some(Equal) + assert!(!(low_uninit > high_uninit)); // Some(Less) + assert!(!(low_uninit > high_init)); // Some(Less) + assert!(!(low_init > high_uninit)); // Some(Less) + assert!(!(low_init > high_init)); // Some(Less) + + // 4. a <= b if and only if a < b || a == b + assert!(low_uninit <= high_uninit); // a < b + assert!(low_uninit <= high_init); // a < b + assert!(low_init <= high_uninit); // a < b + assert!(low_init <= high_init); // a < b + assert!(high_uninit <= high_uninit); // a == b + assert!(high_uninit <= high_init); // a == b + assert!(high_init <= high_uninit); // a == b + assert!(!(high_uninit <= low_uninit)); // a > b + assert!(!(high_uninit <= low_init)); // a > b + assert!(!(high_init <= low_uninit)); // a > b + assert!(!(high_init <= low_init)); // a > b + + // 5. a >= b if and only if a > b || a == b + assert!(high_uninit >= low_uninit); // a > b + assert!(high_uninit >= low_init); // a > b + assert!(high_init >= low_uninit); // a > b + assert!(high_init >= low_init); // a > b + assert!(high_uninit >= high_uninit); // a == b + assert!(high_uninit >= high_init); // a == b + assert!(high_init >= high_uninit); // a == b + assert!(high_init >= high_init); // a == b + assert!(!(low_uninit >= high_uninit)); // a < b + assert!(!(low_uninit >= high_init)); // a < b + assert!(!(low_init >= high_uninit)); // a < b + assert!(!(low_init >= high_init)); // a < b + + // 6. a != b if and only if !(a == b) + assert!(high_uninit != low_uninit); // asserted !(high == low) above + assert!(high_uninit != low_init); // asserted !(high == low) above + assert!(high_init != low_uninit); // asserted !(high == low) above + assert!(high_init != low_init); // asserted !(high == low) above + assert!(low_uninit != high_uninit); // asserted !(low == high) above + assert!(low_uninit != high_init); // asserted !(low == high) above + assert!(low_init != high_uninit); // asserted !(low == high) above + assert!(low_init != high_init); // asserted !(low == high) above + assert!(!(high_uninit != high_uninit)); // asserted high == high above + assert!(!(high_uninit != high_init)); // asserted high == high above + assert!(!(high_init != high_uninit)); // asserted high == high above + assert!(!(high_init != high_init)); // asserted high == high above + } + + #[test] + // From https://doc.rust-lang.org/std/hash/trait.Hash.html#hash-and-eq + fn verification_key_hash_and_eq_should_be_consistent() { + // Check verification keys compare equal if and only if their keys are equal. + let key0 = VerificationKey { + key: SigningKey::from([0; 32]).0.verification_key(), + address: OnceLock::new(), + }; + let other_key0 = VerificationKey { + key: SigningKey::from([0; 32]).0.verification_key(), + address: OnceLock::from(Address::from([0; 20])), + }; + let key1 = VerificationKey { + key: SigningKey::from([1; 32]).0.verification_key(), + address: OnceLock::new(), + }; + + assert!(key0 == other_key0); + assert!(key0 != key1); + + // Check verification keys' std hashes compare equal if and only if their keys are equal. + let std_hash = |verification_key: &VerificationKey| -> u64 { + let mut hasher = std::hash::DefaultHasher::new(); + verification_key.hash(&mut hasher); + hasher.finish() + }; + assert!(std_hash(&key0) == std_hash(&other_key0)); + assert!(std_hash(&key0) != std_hash(&key1)); + } +} diff --git a/crates/astria-core/src/primitive/v1/mod.rs b/crates/astria-core/src/primitive/v1/mod.rs index 3c6b285fa8..13a392c19e 100644 --- a/crates/astria-core/src/primitive/v1/mod.rs +++ b/crates/astria-core/src/primitive/v1/mod.rs @@ -268,23 +268,6 @@ impl Address { self.0.to_vec() } - /// Construct a sequencer address from a [`ed25519_consensus::VerificationKey`]. - /// - /// The first 20 bytes of the sha256 hash of the verification key is the address. - #[must_use] - // Silence the clippy lint because the function body asserts that the panic - // cannot happen. - #[allow(clippy::missing_panics_doc)] - pub fn from_verification_key(public_key: ed25519_consensus::VerificationKey) -> Self { - /// this ensures that `ADDRESS_LEN` is never accidentally changed to a value - /// that would violate this assumption. - #[allow(clippy::assertions_on_constants)] - const _: () = assert!(ADDRESS_LEN <= 32); - let bytes: [u8; 32] = Sha256::digest(public_key).into(); - Self::try_from_slice(&bytes[..ADDRESS_LEN]) - .expect("can convert 32 byte hash to 20 byte array") - } - /// Convert a byte slice to an address. /// /// # Errors diff --git a/crates/astria-core/src/protocol/transaction/v1alpha1/mod.rs b/crates/astria-core/src/protocol/transaction/v1alpha1/mod.rs index 78354b8bfd..25a0d5dcb4 100644 --- a/crates/astria-core/src/protocol/transaction/v1alpha1/mod.rs +++ b/crates/astria-core/src/protocol/transaction/v1alpha1/mod.rs @@ -1,14 +1,15 @@ -use ed25519_consensus::{ - Signature, - VerificationKey, -}; use prost::{ Message as _, Name as _, }; use super::raw; -use crate::crypto::SigningKey; +use crate::crypto::{ + self, + Signature, + SigningKey, + VerificationKey, +}; pub mod action; pub use action::Action; @@ -18,7 +19,7 @@ pub use action::Action; pub struct SignedTransactionError(SignedTransactionErrorKind); impl SignedTransactionError { - fn signature(inner: ed25519_consensus::Error) -> Self { + fn signature(inner: crypto::Error) -> Self { Self(SignedTransactionErrorKind::Signature(inner)) } @@ -26,11 +27,11 @@ impl SignedTransactionError { Self(SignedTransactionErrorKind::Transaction(inner)) } - fn verification(inner: ed25519_consensus::Error) -> Self { + fn verification(inner: crypto::Error) -> Self { Self(SignedTransactionErrorKind::Verification(inner)) } - fn verification_key(inner: ed25519_consensus::Error) -> Self { + fn verification_key(inner: crypto::Error) -> Self { Self(SignedTransactionErrorKind::VerificationKey(inner)) } @@ -44,13 +45,13 @@ enum SignedTransactionErrorKind { #[error("`transaction` field not set")] UnsetTransaction, #[error("`signature` field invalid")] - Signature(#[source] ed25519_consensus::Error), + Signature(#[source] crypto::Error), #[error("`transaction` field invalid")] Transaction(#[source] UnsignedTransactionError), #[error("`public_key` field invalid")] - VerificationKey(#[source] ed25519_consensus::Error), + VerificationKey(#[source] crypto::Error), #[error("transaction could not be verified given the signature and verification key")] - Verification(ed25519_consensus::Error), + Verification(crypto::Error), } /// The individual parts of a [`SignedTransaction`]. @@ -193,8 +194,8 @@ impl SignedTransaction { } #[must_use] - pub fn verification_key(&self) -> VerificationKey { - self.verification_key + pub fn verification_key(&self) -> &VerificationKey { + &self.verification_key } #[must_use] diff --git a/crates/astria-sequencer-client/Cargo.toml b/crates/astria-sequencer-client/Cargo.toml index d9fddaf86d..895770330c 100644 --- a/crates/astria-sequencer-client/Cargo.toml +++ b/crates/astria-sequencer-client/Cargo.toml @@ -29,7 +29,6 @@ http = ["tendermint-rpc/http-client"] websocket = ["tendermint-rpc/websocket-client"] [dev-dependencies] -ed25519-consensus = { workspace = true } hex-literal = { workspace = true } regex = { workspace = true } serde = { workspace = true, features = ["derive"] } diff --git a/crates/astria-sequencer/Cargo.toml b/crates/astria-sequencer/Cargo.toml index 5cc1143015..bda8420479 100644 --- a/crates/astria-sequencer/Cargo.toml +++ b/crates/astria-sequencer/Cargo.toml @@ -36,7 +36,6 @@ cnidarium-component = { git = "https://github.com/penumbra-zone/penumbra.git", r async-trait = { workspace = true } bytes = { workspace = true } -ed25519-consensus = { workspace = true } futures = { workspace = true } hex = { workspace = true, features = ["serde"] } ibc-types = { workspace = true, features = ["with_serde"] } diff --git a/crates/astria-sequencer/src/app/mod.rs b/crates/astria-sequencer/src/app/mod.rs index b5e43651e7..49ac6bd444 100644 --- a/crates/astria-sequencer/src/app/mod.rs +++ b/crates/astria-sequencer/src/app/mod.rs @@ -968,7 +968,7 @@ impl App { /// Executes a signed transaction. #[instrument(name = "App::execute_transaction", skip_all, fields( signed_transaction_hash = %telemetry::display::base64(&signed_tx.sha256_of_proto_encoding()), - sender = %Address::from_verification_key(signed_tx.verification_key()), + sender = %signed_tx.verification_key().address(), ))] pub(crate) async fn execute_transaction( &mut self, @@ -1138,7 +1138,7 @@ async fn update_mempool_after_finalization( match TransactionPriority::new( tx.nonce(), state - .get_account_nonce(Address::from_verification_key(tx.verification_key())) + .get_account_nonce(*tx.verification_key().address()) .await .context("failed to fetch account nonce")?, ) { diff --git a/crates/astria-sequencer/src/app/test_utils.rs b/crates/astria-sequencer/src/app/test_utils.rs index 458ed8b99a..09c42c2f01 100644 --- a/crates/astria-sequencer/src/app/test_utils.rs +++ b/crates/astria-sequencer/src/app/test_utils.rs @@ -47,7 +47,7 @@ pub(crate) fn get_alice_signing_key_and_address() -> (SigningKey, Address) { .try_into() .unwrap(); let alice_signing_key = SigningKey::from(alice_secret_bytes); - let alice = Address::from_verification_key(alice_signing_key.verification_key()); + let alice = *alice_signing_key.verification_key().address(); (alice_signing_key, alice) } @@ -58,7 +58,7 @@ pub(crate) fn get_bridge_signing_key_and_address() -> (SigningKey, Address) { .try_into() .unwrap(); let bridge_signing_key = SigningKey::from(bridge_secret_bytes); - let bridge = Address::from_verification_key(bridge_signing_key.verification_key()); + let bridge = *bridge_signing_key.verification_key().address(); (bridge_signing_key, bridge) } diff --git a/crates/astria-sequencer/src/app/tests_app.rs b/crates/astria-sequencer/src/app/tests_app.rs index bed1e19c92..5dbfc4f02a 100644 --- a/crates/astria-sequencer/src/app/tests_app.rs +++ b/crates/astria-sequencer/src/app/tests_app.rs @@ -765,7 +765,7 @@ async fn update_mempool_after_finalization_update_account_nonce() { // insert tx with nonce 1, account nonce is 0 let tx = get_mock_tx(1); - let address = Address::from_verification_key(tx.verification_key()); + let address = *tx.verification_key().address(); let priority = TransactionPriority::new(1, 0).unwrap(); mempool.insert(tx.clone(), priority).await.unwrap(); @@ -791,7 +791,7 @@ async fn update_mempool_after_finalization_remove_tx_if_nonce_too_low() { // insert tx with nonce 1, account nonce is 1 let tx = get_mock_tx(1); - let address = Address::from_verification_key(tx.verification_key()); + let address = *tx.verification_key().address(); let priority = TransactionPriority::new(1, 1).unwrap(); mempool.insert(tx.clone(), priority).await.unwrap(); diff --git a/crates/astria-sequencer/src/app/tests_execute_transaction.rs b/crates/astria-sequencer/src/app/tests_execute_transaction.rs index 20b5bfe792..ca9c891f6d 100644 --- a/crates/astria-sequencer/src/app/tests_execute_transaction.rs +++ b/crates/astria-sequencer/src/app/tests_execute_transaction.rs @@ -957,7 +957,7 @@ async fn app_stateful_check_fails_insufficient_total_balance() { // create a new key; will have 0 balance let keypair = SigningKey::new(OsRng); - let keypair_address = Address::from_verification_key(keypair.verification_key()); + let keypair_address = *keypair.verification_key().address(); // figure out needed fee for a single transfer let data = b"hello world".to_vec(); diff --git a/crates/astria-sequencer/src/mempool.rs b/crates/astria-sequencer/src/mempool.rs index 3379fbd532..0795dba77c 100644 --- a/crates/astria-sequencer/src/mempool.rs +++ b/crates/astria-sequencer/src/mempool.rs @@ -187,8 +187,8 @@ impl Mempool { let inner = self.inner.read().await; let mut nonce = None; for (tx, priority) in inner.iter() { - let sender = Address::from_verification_key(tx.verification_key()); - if &sender == address { + let sender = tx.verification_key().address(); + if sender == address { nonce = Some(std::cmp::max( nonce.unwrap_or_default(), priority.transaction_nonce, diff --git a/crates/astria-sequencer/src/service/consensus.rs b/crates/astria-sequencer/src/service/consensus.rs index 91f06de2c5..55156fe592 100644 --- a/crates/astria-sequencer/src/service/consensus.rs +++ b/crates/astria-sequencer/src/service/consensus.rs @@ -219,7 +219,10 @@ mod test { }; use astria_core::{ - crypto::SigningKey, + crypto::{ + SigningKey, + VerificationKey, + }, primitive::v1::{ asset::DEFAULT_NATIVE_ASSET_DENOM, Address, @@ -232,7 +235,6 @@ mod test { }, }; use bytes::Bytes; - use ed25519_consensus::VerificationKey; use prost::Message as _; use rand::rngs::OsRng; use tendermint::{ @@ -477,7 +479,7 @@ mod test { async fn new_consensus_service(funded_key: Option) -> (Consensus, Mempool) { let accounts = if funded_key.is_some() { vec![crate::genesis::Account { - address: Address::from_verification_key(funded_key.unwrap()), + address: *funded_key.unwrap().address(), balance: 10u128.pow(19), }] } else { diff --git a/crates/astria-sequencer/src/service/mempool.rs b/crates/astria-sequencer/src/service/mempool.rs index e65241967a..569f65c3fc 100644 --- a/crates/astria-sequencer/src/service/mempool.rs +++ b/crates/astria-sequencer/src/service/mempool.rs @@ -96,7 +96,6 @@ async fn handle_check_tx( state: S, mempool: &mut AppMempool, ) -> response::CheckTx { - use astria_core::primitive::v1::Address; use sha2::Digest as _; let tx_hash = sha2::Sha256::digest(&req.tx).into(); @@ -192,7 +191,7 @@ async fn handle_check_tx( let priority = crate::mempool::TransactionPriority::new( signed_tx.nonce(), state - .get_account_nonce(Address::from_verification_key(signed_tx.verification_key())) + .get_account_nonce(*signed_tx.verification_key().address()) .await .expect("can fetch account nonce"), ) diff --git a/crates/astria-sequencer/src/transaction/checks.rs b/crates/astria-sequencer/src/transaction/checks.rs index 1d3d8a7454..3ee0d91239 100644 --- a/crates/astria-sequencer/src/transaction/checks.rs +++ b/crates/astria-sequencer/src/transaction/checks.rs @@ -31,7 +31,7 @@ pub(crate) async fn check_nonce_mempool( tx: &SignedTransaction, state: &S, ) -> anyhow::Result<()> { - let signer_address = Address::from_verification_key(tx.verification_key()); + let signer_address = *tx.verification_key().address(); let curr_nonce = state .get_account_nonce(signer_address) .await @@ -62,7 +62,7 @@ pub(crate) async fn check_balance_mempool( tx: &SignedTransaction, state: &S, ) -> anyhow::Result<()> { - let signer_address = Address::from_verification_key(tx.verification_key()); + let signer_address = *tx.verification_key().address(); check_balance_for_total_fees(tx.unsigned_transaction(), signer_address, state).await?; Ok(()) } diff --git a/crates/astria-sequencer/src/transaction/mod.rs b/crates/astria-sequencer/src/transaction/mod.rs index 3a03d65cba..23781ea535 100644 --- a/crates/astria-sequencer/src/transaction/mod.rs +++ b/crates/astria-sequencer/src/transaction/mod.rs @@ -49,7 +49,7 @@ pub(crate) async fn check_stateful( tx: &SignedTransaction, state: &S, ) -> anyhow::Result<()> { - let signer_address = Address::from_verification_key(tx.verification_key()); + let signer_address = *tx.verification_key().address(); tx.unsigned_transaction() .check_stateful(state, signer_address) .await @@ -59,7 +59,7 @@ pub(crate) async fn execute( tx: &SignedTransaction, state: &mut S, ) -> anyhow::Result<()> { - let signer_address = Address::from_verification_key(tx.verification_key()); + let signer_address = *tx.verification_key().address(); tx.unsigned_transaction() .execute(state, signer_address) .await