aleph_types/verify_signature/
mod.rs1#[cfg(feature = "signature-evm")]
2pub(crate) mod 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 #[error("Message has no signature to verify")]
32 MissingSignature,
33}
34
35fn verification_buffer(
38 chain: &Chain,
39 sender: &Address,
40 message_type: MessageType,
41 item_hash: &ItemHash,
42) -> String {
43 format!("{chain}\n{sender}\n{message_type}\n{item_hash}")
44}
45
46pub fn verify(
48 chain: &Chain,
49 sender: &Address,
50 signature: &Signature,
51 message_type: MessageType,
52 item_hash: &ItemHash,
53) -> Result<(), SignatureVerificationError> {
54 let buffer = verification_buffer(chain, sender, message_type, item_hash);
55
56 #[cfg(feature = "signature-evm")]
57 if chain.is_evm() {
58 let recovered = ethereum::recover_address(buffer.as_bytes(), signature.as_str())?;
59 let recovered_addr = Address::from(recovered);
60
61 if !sender
62 .as_str()
63 .eq_ignore_ascii_case(recovered_addr.as_str())
64 {
65 return Err(SignatureVerificationError::SignatureMismatch {
66 expected: sender.clone(),
67 recovered: recovered_addr,
68 });
69 }
70
71 return Ok(());
72 }
73
74 #[cfg(feature = "signature-sol")]
75 if chain.is_svm() {
76 return solana::verify(buffer.as_bytes(), signature.as_str(), sender.as_str());
78 }
79
80 Err(SignatureVerificationError::UnsupportedChain(chain.clone()))
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use crate::{address, item_hash};
87
88 #[test]
89 fn test_verification_buffer_format() {
90 let chain = Chain::Ethereum;
91 let sender = address!("0xB68B9D4f3771c246233823ed1D3Add451055F9Ef");
92 let message_type = MessageType::Post;
93 let item_hash =
94 item_hash!("d281eb8a69ba1f4dda2d71aaf3ded06caa92edd690ef3d0632f41aa91167762c");
95
96 let buffer = verification_buffer(&chain, &sender, message_type, &item_hash);
97
98 assert_eq!(
99 buffer,
100 "ETH\n\
101 0xB68B9D4f3771c246233823ed1D3Add451055F9Ef\n\
102 POST\n\
103 d281eb8a69ba1f4dda2d71aaf3ded06caa92edd690ef3d0632f41aa91167762c"
104 );
105 }
106
107 #[test]
108 fn test_verification_buffer_different_chain_and_type() {
109 let chain = Chain::Arbitrum;
110 let sender = address!("0xABCD");
111 let message_type = MessageType::Aggregate;
112 let item_hash =
113 item_hash!("0000000000000000000000000000000000000000000000000000000000000001");
114
115 let buffer = verification_buffer(&chain, &sender, message_type, &item_hash);
116
117 assert_eq!(
118 buffer,
119 "ARB\n0xABCD\nAGGREGATE\n0000000000000000000000000000000000000000000000000000000000000001"
120 );
121 }
122
123 #[cfg(feature = "signature-evm")]
124 #[test]
125 fn test_verify_with_v_zero_format() {
126 let json = include_str!("../../../../fixtures/messages/post/post.json");
129 let mut message: crate::message::Message = serde_json::from_str(json).unwrap();
130
131 let sig = message.signature.as_ref().unwrap().as_str().to_string();
133 let normalized_sig = format!("{}00", &sig[..sig.len() - 2]);
134 message.signature = Some(Signature::from(normalized_sig));
135
136 message.verify_signature().unwrap();
137 }
138}