aleph_types/verify_signature/
mod.rs1#[cfg(feature = "signature-evm")]
2mod ethereum;
3#[cfg(feature = "signature-sol")]
4mod solana;
5
6use crate::chain::{Address, Chain, Signature};
7use crate::item_hash::ItemHash;
8use crate::message::MessageType;
9use thiserror::Error;
10
11#[derive(Error, Debug)]
12#[non_exhaustive]
13pub enum SignatureVerificationError {
14 #[error(
16 "Signature mismatch: message sender is {expected}, but signature was produced by {recovered}"
17 )]
18 SignatureMismatch {
19 expected: Address,
20 recovered: Address,
21 },
22 #[error("Invalid signature: {0}")]
24 InvalidSignature(String),
25 #[error("Unsupported chain for signature verification: {0}")]
27 UnsupportedChain(Chain),
28}
29
30fn verification_buffer(
33 chain: &Chain,
34 sender: &Address,
35 message_type: MessageType,
36 item_hash: &ItemHash,
37) -> String {
38 format!("{chain}\n{sender}\n{message_type}\n{item_hash}")
39}
40
41pub(crate) fn verify(
43 chain: &Chain,
44 sender: &Address,
45 signature: &Signature,
46 message_type: MessageType,
47 item_hash: &ItemHash,
48) -> Result<(), SignatureVerificationError> {
49 let buffer = verification_buffer(chain, sender, message_type, item_hash);
50
51 #[cfg(feature = "signature-evm")]
52 if chain.is_evm() {
53 let recovered = ethereum::recover_address(buffer.as_bytes(), signature.as_str())?;
54 let recovered_addr = Address::from(recovered);
55
56 if !sender
57 .as_str()
58 .eq_ignore_ascii_case(recovered_addr.as_str())
59 {
60 return Err(SignatureVerificationError::SignatureMismatch {
61 expected: sender.clone(),
62 recovered: recovered_addr,
63 });
64 }
65
66 return Ok(());
67 }
68
69 #[cfg(feature = "signature-sol")]
70 if chain.is_svm() {
71 return solana::verify(buffer.as_bytes(), signature.as_str(), sender.as_str());
73 }
74
75 Err(SignatureVerificationError::UnsupportedChain(chain.clone()))
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use crate::{address, item_hash};
82
83 #[test]
84 fn test_verification_buffer_format() {
85 let chain = Chain::Ethereum;
86 let sender = address!("0xB68B9D4f3771c246233823ed1D3Add451055F9Ef");
87 let message_type = MessageType::Post;
88 let item_hash =
89 item_hash!("d281eb8a69ba1f4dda2d71aaf3ded06caa92edd690ef3d0632f41aa91167762c");
90
91 let buffer = verification_buffer(&chain, &sender, message_type, &item_hash);
92
93 assert_eq!(
94 buffer,
95 "ETH\n\
96 0xB68B9D4f3771c246233823ed1D3Add451055F9Ef\n\
97 POST\n\
98 d281eb8a69ba1f4dda2d71aaf3ded06caa92edd690ef3d0632f41aa91167762c"
99 );
100 }
101
102 #[test]
103 fn test_verification_buffer_different_chain_and_type() {
104 let chain = Chain::Arbitrum;
105 let sender = address!("0xABCD");
106 let message_type = MessageType::Aggregate;
107 let item_hash =
108 item_hash!("0000000000000000000000000000000000000000000000000000000000000001");
109
110 let buffer = verification_buffer(&chain, &sender, message_type, &item_hash);
111
112 assert_eq!(
113 buffer,
114 "ARB\n0xABCD\nAGGREGATE\n0000000000000000000000000000000000000000000000000000000000000001"
115 );
116 }
117
118 #[cfg(feature = "signature-evm")]
119 #[test]
120 fn test_verify_with_v_zero_format() {
121 let json = include_str!("../../../../fixtures/messages/post/post.json");
124 let mut message: crate::message::Message = serde_json::from_str(json).unwrap();
125
126 let sig = message.signature.as_str().to_string();
128 let normalized_sig = format!("{}00", &sig[..sig.len() - 2]);
129 message.signature = Signature::from(normalized_sig);
130
131 message.verify_signature().unwrap();
132 }
133}