diff --git a/crates/tx/src/lib.rs b/crates/tx/src/lib.rs index c8268eca14..b399ab72f3 100644 --- a/crates/tx/src/lib.rs +++ b/crates/tx/src/lib.rs @@ -28,12 +28,15 @@ use data::TxType; pub use either; pub use event::new_tx_event; pub use namada_core::key::SignableEthMessage; -pub use sign::SignatureIndex; +pub use sign::{ + standalone_signature, verify_standalone_sig, SignatureIndex, Signed, + VerifySigError, +}; pub use types::{ - standalone_signature, verify_standalone_sig, Authorization, BatchedTx, - BatchedTxRef, Code, Commitment, CompressedAuthorization, Data, DecodeError, - Header, IndexedTx, IndexedTxRange, MaspBuilder, Memo, Section, Signed, - Signer, Tx, TxCommitments, TxError, VerifySigError, + Authorization, BatchedTx, BatchedTxRef, Code, Commitment, + CompressedAuthorization, Data, DecodeError, Header, IndexedTx, + IndexedTxRange, MaspBuilder, Memo, Section, Signer, Tx, TxCommitments, + TxError, }; /// Length of the transaction sections salt diff --git a/crates/tx/src/sign.rs b/crates/tx/src/sign.rs index 366104e187..a77146ea2b 100644 --- a/crates/tx/src/sign.rs +++ b/crates/tx/src/sign.rs @@ -1,15 +1,161 @@ //! Types for signing use std::cmp::Ordering; +use std::collections::BTreeMap; +use std::hash::{Hash, Hasher}; use std::io; +use std::marker::PhantomData; +use borsh::schema::{self, Declaration, Definition}; use namada_core::address::Address; use namada_core::borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; -use namada_core::key::common; +use namada_core::key::{common, SerializeWithBorsh, SigScheme, Signable}; use namada_macros::BorshDeserializer; #[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; +use thiserror::Error; + +/// Represents an error in signature verification +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum VerifySigError { + #[error("{0}")] + VerifySig(#[from] namada_core::key::VerifySigError), + #[error("{0}")] + Gas(#[from] namada_gas::Error), + #[error("The wrapper signature is invalid.")] + InvalidWrapperSignature, + #[error("The section signature is invalid: {0}")] + InvalidSectionSignature(String), + #[error("The number of PKs overflows u8::MAX")] + PksOverflow, + #[error("An expected signature is missing.")] + MissingSignature, +} + +/// A generic signed data wrapper for serialize-able types. +/// +/// The default serialization method is [`BorshSerialize`]. +#[derive( + Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +pub struct Signed { + /// Arbitrary data to be signed + pub data: T, + /// The signature of the data + pub sig: common::Signature, + /// The method to serialize the data with, + /// before it being signed + _serialization: PhantomData, +} + +impl Eq for Signed {} + +impl PartialEq for Signed { + fn eq(&self, other: &Self) -> bool { + self.data == other.data && self.sig == other.sig + } +} + +impl Hash for Signed { + fn hash(&self, state: &mut H) { + self.data.hash(state); + self.sig.hash(state); + } +} + +impl PartialOrd for Signed { + fn partial_cmp(&self, other: &Self) -> Option { + self.data.partial_cmp(&other.data) + } +} +impl Ord for Signed { + fn cmp(&self, other: &Self) -> Ordering { + self.data.cmp(&other.data) + } +} + +impl BorshSchema for Signed { + fn add_definitions_recursively( + definitions: &mut BTreeMap, + ) { + let fields = schema::Fields::NamedFields(vec![ + ("data".to_string(), T::declaration()), + ("sig".to_string(), ::declaration()), + ]); + let definition = schema::Definition::Struct { fields }; + schema::add_definition(Self::declaration(), definition, definitions); + T::add_definitions_recursively(definitions); + ::add_definitions_recursively(definitions); + } + + fn declaration() -> schema::Declaration { + format!("Signed<{}>", T::declaration()) + } +} + +impl Signed { + /// Initialize a new [`Signed`] instance from an existing signature. + #[inline] + pub fn new_from(data: T, sig: common::Signature) -> Self { + Self { + data, + sig, + _serialization: PhantomData, + } + } +} + +impl> Signed { + /// Initialize a new [`Signed`] instance. + pub fn new(keypair: &common::SecretKey, data: T) -> Self { + let to_sign = S::as_signable(&data); + let sig = + common::SigScheme::sign_with_hasher::(keypair, to_sign); + Self::new_from(data, sig) + } + + /// Verify that the data has been signed by the secret key + /// counterpart of the given public key. + pub fn verify( + &self, + pk: &common::PublicKey, + ) -> std::result::Result<(), VerifySigError> { + let signed_bytes = S::as_signable(&self.data); + common::SigScheme::verify_signature_with_hasher::( + pk, + &signed_bytes, + &self.sig, + ) + .map_err(Into::into) + } +} + +/// Get a signature for data +pub fn standalone_signature>( + keypair: &common::SecretKey, + data: &T, +) -> common::Signature { + let to_sign = S::as_signable(data); + common::SigScheme::sign_with_hasher::(keypair, to_sign) +} + +/// Verify that the input data has been signed by the secret key +/// counterpart of the given public key. +pub fn verify_standalone_sig>( + data: &T, + pk: &common::PublicKey, + sig: &common::Signature, +) -> std::result::Result<(), VerifySigError> { + let signed_data = S::as_signable(data); + common::SigScheme::verify_signature_with_hasher::( + pk, + &signed_data, + sig, + ) + .map_err(Into::into) +} #[derive( Clone, @@ -91,7 +237,7 @@ mod test { let sk = key::testing::keypair_1(); let pubkey = sk.to_public(); let data = [0_u8]; - let signature = key::common::SigScheme::sign(&sk, &data); + let signature = key::common::SigScheme::sign(&sk, data); let sig_index = SignatureIndex { pubkey, index: Some((address::testing::established_address_1(), 1)), diff --git a/crates/tx/src/types.rs b/crates/tx/src/types.rs index 04a043b0ce..cdf77f6ac6 100644 --- a/crates/tx/src/types.rs +++ b/crates/tx/src/types.rs @@ -1,8 +1,6 @@ use std::borrow::Cow; -use std::cmp::Ordering; use std::collections::BTreeMap; -use std::hash::{Hash, Hasher}; -use std::marker::PhantomData; +use std::hash::Hash; use std::ops::{Bound, RangeBounds}; use data_encoding::HEXUPPER; @@ -12,7 +10,6 @@ use masp_primitives::transaction::Transaction; use masp_primitives::zip32::ExtendedFullViewingKey; use namada_account::AccountPublicKeysMap; use namada_core::address::Address; -use namada_core::borsh::schema::{add_definition, Declaration, Definition}; use namada_core::borsh::{ self, BorshDeserialize, BorshSchema, BorshSerialize, BorshSerializeExt, }; @@ -32,27 +29,9 @@ use thiserror::Error; use crate::data::protocol::ProtocolTx; use crate::data::{hash_tx, Fee, GasLimit, TxType, WrapperTx}; -use crate::sign::SignatureIndex; +use crate::sign::{SignatureIndex, VerifySigError}; use crate::{hex_data_serde, hex_salt_serde, proto, SALT_LENGTH}; -/// Represents an error in signature verification -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum VerifySigError { - #[error("{0}")] - VerifySig(#[from] namada_core::key::VerifySigError), - #[error("{0}")] - Gas(#[from] namada_gas::Error), - #[error("The wrapper signature is invalid.")] - InvalidWrapperSignature, - #[error("The section signature is invalid: {0}")] - InvalidSectionSignature(String), - #[error("The number of PKs overflows u8::MAX")] - PksOverflow, - #[error("An expected signature is missing.")] - MissingSignature, -} - #[allow(missing_docs)] #[derive(Error, Debug)] pub enum DecodeError { @@ -72,129 +51,6 @@ pub enum DecodeError { InvalidJSONDeserialization(String), } -/// A generic signed data wrapper for serialize-able types. -/// -/// The default serialization method is [`BorshSerialize`]. -#[derive( - Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize, -)] -pub struct Signed { - /// Arbitrary data to be signed - pub data: T, - /// The signature of the data - pub sig: common::Signature, - /// The method to serialize the data with, - /// before it being signed - _serialization: PhantomData, -} - -impl Eq for Signed {} - -impl PartialEq for Signed { - fn eq(&self, other: &Self) -> bool { - self.data == other.data && self.sig == other.sig - } -} - -impl Hash for Signed { - fn hash(&self, state: &mut H) { - self.data.hash(state); - self.sig.hash(state); - } -} - -impl PartialOrd for Signed { - fn partial_cmp(&self, other: &Self) -> Option { - self.data.partial_cmp(&other.data) - } -} -impl Ord for Signed { - fn cmp(&self, other: &Self) -> Ordering { - self.data.cmp(&other.data) - } -} - -impl BorshSchema for Signed { - fn add_definitions_recursively( - definitions: &mut BTreeMap, - ) { - let fields = borsh::schema::Fields::NamedFields(vec![ - ("data".to_string(), T::declaration()), - ("sig".to_string(), ::declaration()), - ]); - let definition = borsh::schema::Definition::Struct { fields }; - add_definition(Self::declaration(), definition, definitions); - T::add_definitions_recursively(definitions); - ::add_definitions_recursively(definitions); - } - - fn declaration() -> borsh::schema::Declaration { - format!("Signed<{}>", T::declaration()) - } -} - -impl Signed { - /// Initialize a new [`Signed`] instance from an existing signature. - #[inline] - pub fn new_from(data: T, sig: common::Signature) -> Self { - Self { - data, - sig, - _serialization: PhantomData, - } - } -} - -impl> Signed { - /// Initialize a new [`Signed`] instance. - pub fn new(keypair: &common::SecretKey, data: T) -> Self { - let to_sign = S::as_signable(&data); - let sig = - common::SigScheme::sign_with_hasher::(keypair, to_sign); - Self::new_from(data, sig) - } - - /// Verify that the data has been signed by the secret key - /// counterpart of the given public key. - pub fn verify( - &self, - pk: &common::PublicKey, - ) -> std::result::Result<(), VerifySigError> { - let signed_bytes = S::as_signable(&self.data); - common::SigScheme::verify_signature_with_hasher::( - pk, - &signed_bytes, - &self.sig, - ) - .map_err(Into::into) - } -} - -/// Get a signature for data -pub fn standalone_signature>( - keypair: &common::SecretKey, - data: &T, -) -> common::Signature { - let to_sign = S::as_signable(data); - common::SigScheme::sign_with_hasher::(keypair, to_sign) -} - -/// Verify that the input data has been signed by the secret key -/// counterpart of the given public key. -pub fn verify_standalone_sig>( - data: &T, - pk: &common::PublicKey, - sig: &common::Signature, -) -> std::result::Result<(), VerifySigError> { - let signed_data = S::as_signable(data); - common::SigScheme::verify_signature_with_hasher::( - pk, - &signed_data, - sig, - ) - .map_err(Into::into) -} - /// A section representing transaction data #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(