#[cfg(feature = "signature-evm")]
pub(crate) mod ethereum;
#[cfg(feature = "signature-sol")]
mod solana;
use crate::chain::{Address, Chain, Signature};
use crate::item_hash::ItemHash;
use crate::message::MessageType;
use thiserror::Error;
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum SignatureVerificationError {
#[error(
"Signature mismatch: message sender is {expected}, but signature was produced by {recovered}"
)]
SignatureMismatch {
expected: Address,
recovered: Address,
},
#[error("Invalid signature: {0}")]
InvalidSignature(String),
#[error("Unsupported chain for signature verification: {0}")]
UnsupportedChain(Chain),
}
fn verification_buffer(
chain: &Chain,
sender: &Address,
message_type: MessageType,
item_hash: &ItemHash,
) -> String {
format!("{chain}\n{sender}\n{message_type}\n{item_hash}")
}
pub fn verify(
chain: &Chain,
sender: &Address,
signature: &Signature,
message_type: MessageType,
item_hash: &ItemHash,
) -> Result<(), SignatureVerificationError> {
let buffer = verification_buffer(chain, sender, message_type, item_hash);
#[cfg(feature = "signature-evm")]
if chain.is_evm() {
let recovered = ethereum::recover_address(buffer.as_bytes(), signature.as_str())?;
let recovered_addr = Address::from(recovered);
if !sender
.as_str()
.eq_ignore_ascii_case(recovered_addr.as_str())
{
return Err(SignatureVerificationError::SignatureMismatch {
expected: sender.clone(),
recovered: recovered_addr,
});
}
return Ok(());
}
#[cfg(feature = "signature-sol")]
if chain.is_svm() {
return solana::verify(buffer.as_bytes(), signature.as_str(), sender.as_str());
}
Err(SignatureVerificationError::UnsupportedChain(chain.clone()))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{address, item_hash};
#[test]
fn test_verification_buffer_format() {
let chain = Chain::Ethereum;
let sender = address!("0xB68B9D4f3771c246233823ed1D3Add451055F9Ef");
let message_type = MessageType::Post;
let item_hash =
item_hash!("d281eb8a69ba1f4dda2d71aaf3ded06caa92edd690ef3d0632f41aa91167762c");
let buffer = verification_buffer(&chain, &sender, message_type, &item_hash);
assert_eq!(
buffer,
"ETH\n\
0xB68B9D4f3771c246233823ed1D3Add451055F9Ef\n\
POST\n\
d281eb8a69ba1f4dda2d71aaf3ded06caa92edd690ef3d0632f41aa91167762c"
);
}
#[test]
fn test_verification_buffer_different_chain_and_type() {
let chain = Chain::Arbitrum;
let sender = address!("0xABCD");
let message_type = MessageType::Aggregate;
let item_hash =
item_hash!("0000000000000000000000000000000000000000000000000000000000000001");
let buffer = verification_buffer(&chain, &sender, message_type, &item_hash);
assert_eq!(
buffer,
"ARB\n0xABCD\nAGGREGATE\n0000000000000000000000000000000000000000000000000000000000000001"
);
}
#[cfg(feature = "signature-evm")]
#[test]
fn test_verify_with_v_zero_format() {
let json = include_str!("../../../../fixtures/messages/post/post.json");
let mut message: crate::message::Message = serde_json::from_str(json).unwrap();
let sig = message.signature.as_str().to_string();
let normalized_sig = format!("{}00", &sig[..sig.len() - 2]);
message.signature = Signature::from(normalized_sig);
message.verify_signature().unwrap();
}
}