Created
August 8, 2025 15:10
-
-
Save target-san/4eaef69b0c94cb32e47b8231c777c797 to your computer and use it in GitHub Desktop.
Parse `reown` signed message with `alloy-primitives`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! Compatible with `cargo eval` | |
//! ```cargo | |
//! [dependencies] | |
//! alloy-primitives = { version = "1.3.0", features=["k256"] } | |
//! anyhow = "1.0.98" | |
//! ``` | |
use alloy_primitives::{eip191_hash_message, keccak256, Address, Bytes, Signature, B256}; | |
use anyhow::{Result, ensure, anyhow, bail}; | |
const ADDRESS: &str = "0x181cD97461ACf9e863fE5cCf8f432E0Fbba678bC"; | |
const CHALLENGE: &str = "Hello Reown AppKit!"; | |
const SIGNATURE: &str = concat!("0x", | |
"0000000000000000000000004e1dcf7ad4e460cfd30791ccc4f9c8a4f820ec67", | |
"0000000000000000000000000000000000000000000000000000000000000060", | |
"00000000000000000000000000000000000000000000000000000000000003c0", | |
"0000000000000000000000000000000000000000000000000000000000000324", | |
"1688f0b900000000000000000000000041675c099f32341bf84bfc5382af534d", | |
"f5c7461a00000000000000000000000000000000000000000000000000000000", | |
"00000060000000000000000000000000000000000000000000007a6733696a79", | |
"3000000000000000000000000000000000000000000000000000000000000000", | |
"00000284b63e800d000000000000000000000000000000000000000000000000", | |
"0000000000000100000000000000000000000000000000000000000000000000", | |
"000000000000000100000000000000000000000038869bf66a61cf6bdb996a6a", | |
"e40d5853fd43b526000000000000000000000000000000000000000000000000", | |
"0000000000000140000000000000000000000000a581c4a4db7175302464ff3c", | |
"06380bc3270b4037000000000000000000000000000000000000000000000000", | |
"0000000000000000000000000000000000000000000000000000000000000000", | |
"0000000000000000000000000000000000000000000000000000000000000000", | |
"0000000000000000000000000000000000000000000000000000000000000000", | |
"0000000000000001000000000000000000000000f3100e7a820ffa74a1b00fbe", | |
"cabb0d8a7c6db41e000000000000000000000000000000000000000000000000", | |
"00000000000001048d80ff0a0000000000000000000000000000000000000000", | |
"0000000000000000000000200000000000000000000000000000000000000000", | |
"0000000000000000000000b9018ecd4ec46d4d2a6b64fe960b3d64e8b94b2234", | |
"eb00000000000000000000000000000000000000000000000000000000000000", | |
"0000000000000000000000000000000000000000000000000000000000000000", | |
"648d0dc49f000000000000000000000000000000000000000000000000000000", | |
"0000000020000000000000000000000000000000000000000000000000000000", | |
"0000000001000000000000000000000000a581c4a4db7175302464ff3c06380b", | |
"c3270b4037000000000000000000000000000000000000000000000000000000", | |
"0000000000000000000000000000000000000000000000000000000000000000", | |
"0000000000000000000000000000000000000000000000000000000000000000", | |
"0000000000000000000000000000000000000000000000000000000000000041", | |
"475d96f8e71b808d5097743020f9e163fdeef769608fb5640c202d8a82f34b6d", | |
"05c1676631b16751e9144028a76e2d4ce46908ff7c812daac946ea7fd5fa9d19", | |
"2000000000000000000000000000000000000000000000000000000000000000", | |
"6492649264926492649264926492649264926492649264926492649264926492"); | |
fn main() { | |
let address: Address = ADDRESS.parse().unwrap(); | |
let challenge: Bytes = CHALLENGE.as_bytes().into(); | |
let signature: Bytes = SIGNATURE.parse().unwrap(); | |
let signature = read_signature(&signature).unwrap(); | |
let hashers: [&dyn Fn(&Bytes) -> B256; 2] = [&hash_raw, &hash_eip191]; | |
println!("Address: {address}"); | |
println!("Signature: {signature}"); | |
println!("Challenge: {challenge}"); | |
for func in &hashers { | |
let hash = func(&challenge); | |
println!(" Hash: {hash}"); | |
let pubkey = signature.recover_from_prehash(&hash).unwrap(); | |
let recovered = Address::from_public_key(&pubkey); | |
if recovered == address { | |
println!(" Recovered: {recovered} (OK)"); | |
} else { | |
println!(" Recovered: {recovered} (FAIL)"); | |
} | |
} | |
} | |
fn hash_raw(bytes: &Bytes) -> B256 { | |
keccak256(bytes) | |
} | |
fn hash_eip191(bytes: &Bytes) -> B256 { | |
eip191_hash_message(bytes) | |
} | |
const EIP_6492_MAGIC: [u8; 32] = [ | |
0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, | |
0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, | |
]; | |
fn read_signature(data: &[u8]) -> Result<Signature> { | |
// Our pseudo-EIP-6492 takes at least 32+3*32+32 bytes | |
match data.len() { | |
65 => { | |
// First, try normal ECDSA | |
Signature::from_raw(data).map_err(|e| { | |
anyhow!("signature - not a valid ECDSA, {e}") | |
}) | |
} | |
192.. => { | |
// Check that first 32 bytes are padded factory wallet, i.e. first 12 are 0 | |
ensure!( | |
data[..12] == [0; 12], | |
anyhow!("signature - not a valid ERC-6492, first 12 bytes must be zero") | |
); | |
ensure!( | |
Address::try_from(&data[12..32]).is_ok(), | |
anyhow!("signature - not a valid ERC-6492, bytes 12..32 must be a valid wallet address") | |
); | |
ensure!( | |
data[data.len() - 32..] == EIP_6492_MAGIC, | |
anyhow!("signature - not a valid ERC-6492, last 32 bytes must match EIP-6492 magic") | |
); | |
// Fetch signature from 3 32-bit chunks before magic suffix, start aligned | |
Signature::from_raw(&data[data.len() - 129..data.len() - 64]).map_err(|e| { | |
anyhow!("signature - not a valid EIP-6492, failed to read embedded ECDSA {e}") | |
}) | |
} | |
_ => { | |
bail!("signature - not a valid ECDSA or EIP-6492, invalid length"); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment