Skip to content

Instantly share code, notes, and snippets.

@target-san
Created August 8, 2025 15:10
Show Gist options
  • Save target-san/4eaef69b0c94cb32e47b8231c777c797 to your computer and use it in GitHub Desktop.
Save target-san/4eaef69b0c94cb32e47b8231c777c797 to your computer and use it in GitHub Desktop.
Parse `reown` signed message with `alloy-primitives`
//! 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