alibi_plugins/siwe/
crypto.rs1use super::config::{SiweCallbackResult, SiweVerification, SiweVerifier};
2use super::parse::valid_address;
3use async_trait::async_trait;
4use k256::ecdsa::{RecoveryId, Signature, VerifyingKey};
5use k256::elliptic_curve::scalar::IsHigh;
6use sha3::{Digest, Keccak256};
7
8#[derive(Debug, Clone, Copy, Default)]
11pub struct Eip191Verifier;
12
13impl Eip191Verifier {
14 #[must_use]
17 pub fn recover_address(message: &str, signature: &str) -> Option<String> {
18 Self::recover_hash_address(ðereum_message_hash(message), signature)
19 }
20
21 #[must_use]
24 pub fn recover_hash_address(digest: &[u8; 32], signature: &str) -> Option<String> {
25 use std::fmt::Write;
26
27 let bytes = hex_bytes(signature)?;
28 let (signature_3, recovery) = match bytes.len() {
29 65 => {
30 let signature_2 = Signature::from_slice(bytes.get(..64)?).ok()?;
31 let encoded = *bytes.get(64)?;
32 let recovery = match encoded {
33 0 | 1 => encoded,
34 27 | 28 => encoded - 27,
35 _ => return None,
36 };
37 (signature_2, recovery)
38 }
39 64 => {
40 let mut compact: [u8; 64] = bytes.try_into().ok()?;
41 let recovery = *compact.get(32)? >> 7;
42 if let Some(first) = compact.get_mut(32) {
43 *first &= 127;
44 }
45 (Signature::from_slice(&compact).ok()?, recovery)
46 }
47 _ => return None,
48 };
49 if signature_3.s().is_high().into() {
52 return None;
53 }
54 let key = VerifyingKey::recover_from_prehash(
55 digest,
56 &signature_3,
57 RecoveryId::try_from(recovery).ok()?,
58 )
59 .ok()?;
60 let public_key = key.to_sec1_point(false);
61 let hash = Keccak256::digest(public_key.as_bytes().get(1..)?);
62 let mut address = String::from("0x");
63
64 for byte in hash.get(12..)? {
65 write!(address, "{byte:02x}").ok()?;
66 }
67 checksum_address(&address)
68 }
69}
70
71#[async_trait]
72impl SiweVerifier for Eip191Verifier {
73 async fn verify_message(&self, input: SiweVerification) -> SiweCallbackResult<bool> {
74 Ok(Self::recover_address(&input.message, &input.signature)
75 .is_some_and(|address| address == input.address))
76 }
77}
78
79#[must_use]
82pub fn ethereum_message_hash(message: &str) -> [u8; 32] {
83 let mut digest = Keccak256::new();
84 digest.update(b"\x19Ethereum Signed Message:\n");
85 digest.update(message.len().to_string().as_bytes());
86 digest.update(message.as_bytes());
87 digest.finalize().into()
88}
89
90pub(super) fn checksum_address(address: &str) -> Option<String> {
91 if !valid_address(address) {
92 return None;
93 }
94 let lower = address.get(2..)?.to_ascii_lowercase();
95 let digest = Keccak256::digest(lower.as_bytes());
96 let mut checksum = String::from("0x");
97 for (index, character) in lower.bytes().enumerate() {
98 let byte = *digest.get(index / 2)?;
99 let nibble = if index % 2 == 0 { byte >> 4 } else { byte & 15 };
100 checksum.push(char::from(if nibble >= 8 {
101 character.to_ascii_uppercase()
102 } else {
103 character
104 }));
105 }
106 Some(checksum)
107}
108
109fn hex_bytes(value: &str) -> Option<Vec<u8>> {
110 let value = value.strip_prefix("0x")?;
111 if !value.len().is_multiple_of(2) {
112 return None;
113 }
114 value
115 .as_bytes()
116 .as_chunks::<2>()
117 .0
118 .iter()
119 .map(|pair| {
120 let upper = char::from(*pair.first()?).to_digit(16)?;
121 let lower = char::from(*pair.get(1)?).to_digit(16)?;
122 u8::try_from((upper << 4) | lower).ok()
123 })
124 .collect()
125}