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