xmtp_cryptography/
signature.rs1use std::array::TryFromSliceError;
2
3use alloy::primitives::{self as alloy_types, Address};
4use hex::FromHexError;
5use serde::{Deserialize, Serialize};
6use thiserror::Error;
7
8use crate::{Secret, configuration::ED25519_KEY_LENGTH};
9
10pub fn to_public_key(private_key: &Secret) -> Result<[u8; ED25519_KEY_LENGTH], TryFromSliceError> {
11 let private_key = private_key.as_slice().try_into()?;
12 let mut computed_public_key = [0u8; ED25519_KEY_LENGTH];
13 libcrux_ed25519::secret_to_public(&mut computed_public_key, &private_key);
14 Ok(computed_public_key)
15}
16
17#[derive(Error, Debug)]
18pub enum SignatureError {
19 #[error("Bad address format")]
20 BadAddressFormat(#[from] hex::FromHexError),
21 #[error("supplied signature is not in the proper format")]
22 BadSignatureFormat(#[from] alloy_types::SignatureError),
23 #[error("Signature is not valid for {addr:?}")]
24 BadSignature { addr: String },
25 #[error(transparent)]
26 Signer(#[from] alloy::signers::Error),
27 #[error("unknown data store error")]
28 Unknown,
29}
30
31#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
32pub enum RecoverableSignature {
33 Eip191Signature(Vec<u8>),
37}
38
39impl RecoverableSignature {
40 pub fn recover_address(&self, predigest_message: &str) -> Result<String, SignatureError> {
41 match self {
42 Self::Eip191Signature(signature_bytes) => {
43 let signature = alloy_types::Signature::try_from(signature_bytes.as_slice())?;
44 let addr = signature.recover_address_from_msg(predigest_message)?;
45 Ok(addr.to_string())
46 }
47 }
48 }
49}
50
51impl From<Vec<u8>> for RecoverableSignature {
52 fn from(value: Vec<u8>) -> Self {
53 RecoverableSignature::Eip191Signature(value)
54 }
55}
56
57impl From<RecoverableSignature> for Vec<u8> {
58 fn from(value: RecoverableSignature) -> Self {
59 match value {
60 RecoverableSignature::Eip191Signature(bytes) => bytes,
61 }
62 }
63}
64impl From<alloy::primitives::Signature> for RecoverableSignature {
75 fn from(value: alloy::primitives::Signature) -> Self {
76 RecoverableSignature::Eip191Signature(value.as_bytes().to_vec())
77 }
78}
79
80pub fn h160addr_to_string(bytes: Address) -> String {
81 let mut s = String::from("0x");
82 s.push_str(&hex::encode(bytes));
83 s.to_lowercase()
84}
85
86pub fn is_valid_ethereum_address<S: AsRef<str>>(address: S) -> bool {
88 let address = address.as_ref();
89 let address = address.strip_prefix("0x").unwrap_or(address);
90
91 if address.len() != 40 {
92 return false;
93 }
94
95 address.chars().all(|c| c.is_ascii_hexdigit())
96}
97
98#[derive(Debug, Error)]
99pub enum IdentifierValidationError {
100 #[error("invalid addresses: {0:?}")]
101 InvalidAddresses(Vec<String>),
102 #[error("address is invalid hex address")]
103 HexDecode(#[from] FromHexError),
104 #[error("generic error: {0}")]
105 Generic(String),
106}
107
108pub fn sanitize_evm_addresses(
109 account_addresses: &[impl AsRef<str>],
110) -> Result<Vec<String>, IdentifierValidationError> {
111 let mut invalid = account_addresses
112 .iter()
113 .filter(|a| !is_valid_ethereum_address(a))
114 .peekable();
115
116 if invalid.peek().is_some() {
117 return Err(IdentifierValidationError::InvalidAddresses(
118 invalid
119 .map(|addr| addr.as_ref().to_string())
120 .collect::<Vec<_>>(),
121 ));
122 }
123
124 Ok(account_addresses
125 .iter()
126 .map(|addr| addr.as_ref().to_lowercase())
127 .collect())
128}
129
130#[cfg(test)]
131pub mod tests {
132 use super::is_valid_ethereum_address;
133
134 use alloy::signers::SignerSync;
135 use alloy::signers::local::PrivateKeySigner;
136
137 pub fn generate_random_signature(msg: &str) -> (String, Vec<u8>) {
138 let signer = PrivateKeySigner::random();
139 let signature = signer.sign_message_sync(msg.as_bytes()).unwrap();
140 (hex::encode(signer.address()), signature.as_bytes().to_vec())
141 }
142
143 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
144 #[cfg_attr(not(target_arch = "wasm32"), test)]
145 fn test_eth_address() {
146 assert!(is_valid_ethereum_address(
147 "0x7e57Aed10441c8879ce08E45805EC01Ee9689c9f"
148 ));
149 assert!(!is_valid_ethereum_address("123"));
150 }
151}