diff --git a/Cargo.lock b/Cargo.lock index ac493f0e..2e7369dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -168,15 +168,16 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "0.8.12" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2364c782a245cf8725ea6dbfca5f530162702b5d685992ea03ce64529136cc" +checksum = "41056bde53ae10ffbbf11618efbe1e0290859e5eab0fe9ef82ebdb62f12a866f" dependencies = [ "alloy-json-abi", "alloy-primitives", "alloy-sol-type-parser", "alloy-sol-types", "const-hex", + "derive_more", "itoa", "serde", "serde_json", @@ -238,9 +239,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "0.8.12" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84c506bf264110fa7e90d9924f742f40ef53c6572ea56a0b0bd714a567ed389" +checksum = "c357da577dfb56998d01f574d81ad7a1958d248740a7981b205d69d65a7da404" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -298,9 +299,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.8.12" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fce5dbd6a4f118eecc4719eaa9c7ffc31c315e6c5ccde3642db927802312425" +checksum = "6259a506ab13e1d658796c31e6e39d2e2ee89243bcc505ddc613b35732e0a430" dependencies = [ "alloy-rlp", "bytes", @@ -522,9 +523,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "0.8.12" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9343289b4a7461ed8bab8618504c995c049c082b70c7332efd7b32125633dc05" +checksum = "d9d64f851d95619233f74b310f12bcf16e0cbc27ee3762b6115c14a84809280a" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -536,9 +537,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "0.8.12" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4222d70bec485ceccc5d8fd4f2909edd65b5d5e43d4aca0b5dcee65d519ae98f" +checksum = "6bf7ed1574b699f48bf17caab4e6e54c6d12bc3c006ab33d58b1e227c1c3559f" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", @@ -555,9 +556,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-input" -version = "0.8.12" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e17f2677369571b976e51ea1430eb41c3690d344fef567b840bfc0b01b6f83a" +checksum = "8c02997ccef5f34f9c099277d4145f183b422938ed5322dc57a089fe9b9ad9ee" dependencies = [ "alloy-json-abi", "const-hex", @@ -572,9 +573,9 @@ dependencies = [ [[package]] name = "alloy-sol-type-parser" -version = "0.8.12" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa64d80ae58ffaafdff9d5d84f58d03775f66c84433916dc9a64ed16af5755da" +checksum = "ce13ff37285b0870d0a0746992a4ae48efaf34b766ae4c2640fa15e5305f8e73" dependencies = [ "serde", "winnow", @@ -582,9 +583,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "0.8.12" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6520d427d4a8eb7aa803d852d7a52ceb0c519e784c292f64bb339e636918cf27" +checksum = "1174cafd6c6d810711b4e00383037bdb458efc4fe3dbafafa16567e0320c54d8" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -1451,9 +1452,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0121754e84117e65f9d90648ee6aa4882a6e63110307ab73967a4c5e7e69e586" +checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c" dependencies = [ "cfg-if", "cpufeatures", @@ -5159,6 +5160,8 @@ name = "snapchain" version = "0.1.0" dependencies = [ "alloy", + "alloy-dyn-abi", + "alloy-rlp", "alloy-sol-types", "alloy-transport", "async-trait", @@ -5325,9 +5328,9 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "0.8.12" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f76fe0a3e1476bdaa0775b9aec5b869ed9520c2b2fedfe9c6df3618f8ea6290b" +checksum = "219389c1ebe89f8333df8bdfb871f6631c552ff399c23cac02480b6088aad8f0" dependencies = [ "paste", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 2e122d1d..77c609db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,8 @@ thiserror = "1.0.66" reqwest = { version = "0.12.9", features = ["json"] } figment = { version = "0.10.19", features = ["env", "toml"] } alloy = { version = "0.5.4", features = ["full"] } +alloy-dyn-abi = { version = "0.8.15", features = ["eip712"] } +alloy-rlp = "0.3.9" futures-util = "0.3.31" url = "2.5.3" alloy-transport = "0.5.4" diff --git a/src/core/message.rs b/src/core/message.rs index bf5413d9..cc959a50 100644 --- a/src/core/message.rs +++ b/src/core/message.rs @@ -1,6 +1,11 @@ +use alloy_dyn_abi::TypedData; +use serde_json::json; + use crate::proto; use crate::proto::MessageType; +use super::error::HubError; + impl proto::Message { pub fn is_type(&self, message_type: proto::MessageType) -> bool { self.data.is_some() && self.data.as_ref().unwrap().r#type == message_type as i32 @@ -40,3 +45,151 @@ impl proto::ValidatorMessage { 0 } } + +impl proto::FnameTransfer { + pub fn verify_signature(&self) -> Result { + let proof = self.proof.as_ref().unwrap(); + let username = std::str::from_utf8(&proof.name); + if username.is_err() { + return Err(HubError::validation_failure( + "could not deserialize username", + )); + } + + let json = json!({ + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "UserNameProof": [ + { "name": "name", "type": "string" }, + { "name": "timestamp", "type": "uint256" }, + { "name": "owner", "type": "address" } + ] + }, + "primaryType": "UserNameProof", + "domain": { + "name": "Farcaster name verification", + "version": "1", + "chainId": 1, + "verifyingContract": "0xe3be01d99baa8db9905b33a3ca391238234b79d1" // name registry contract, will be the farcaster ENS CCIP contract later + }, + "message": { + "name": username.unwrap(), + "timestamp": proof.timestamp, + "owner": hex::encode(proof.owner.clone()) + } + }); + + let typed_data = serde_json::from_value::(json); + if typed_data.is_err() { + return Err(HubError::validation_failure( + "could not construct typed data", + )); + } + + let data = typed_data.unwrap(); + let prehash = data.eip712_signing_hash(); + if prehash.is_err() { + return Err(HubError::validation_failure( + "could not construct hash from typed data", + )); + } + + if proof.signature.len() != 65 { + return Err(HubError::validation_failure("invalid signature length")); + } + + let hash = prehash.unwrap(); + let fname_signer = alloy::primitives::address!("Bc5274eFc266311015793d89E9B591fa46294741"); + let signature = alloy::primitives::PrimitiveSignature::from_bytes_and_parity( + &proof.signature[0..64], + proof.signature[64] != 0x1b, + ); + let recovered_address = signature.recover_address_from_prehash(&hash); + if recovered_address.is_err() { + return Err(HubError::validation_failure("could not recover address")); + } + let recovered = recovered_address.unwrap(); + return Ok(recovered == fname_signer); + } +} + +#[cfg(test)] +mod tests { + use proto::{FnameTransfer, UserNameProof}; + + use super::*; + + #[test] + fn test_fname_transfer_verify_valid_signature() { + let transfer = &FnameTransfer{ + id: 1, + from_fid: 1, + proof: Some(UserNameProof{ + timestamp: 1628882891, + name: "farcaster".into(), + owner: hex::decode("8773442740c17c9d0f0b87022c722f9a136206ed").unwrap(), + signature: hex::decode("b7181760f14eda0028e0b647ff15f45235526ced3b4ae07fcce06141b73d32960d3253776e62f761363fb8137087192047763f4af838950a96f3885f3c2289c41b").unwrap(), + fid: 1, + r#type: 1, + }) + }; + let result = transfer.verify_signature(); + assert!(result.is_ok()); + assert!(result.unwrap()); + } + + #[test] + fn test_fname_transfer_verify_wrong_address_for_signature_fails() { + let transfer = &FnameTransfer{ + id: 1, + from_fid: 1, + proof: Some(UserNameProof{ + timestamp: 1628882891, + name: "farcaster".into(), + owner: hex::decode("8773442740c17c9d0f0b87022c722f9a136206ed").unwrap(), + signature: hex::decode("a7181760f14eda0028e0b647ff15f45235526ced3b4ae07fcce06141b73d32960d3253776e62f761363fb8137087192047763f4af838950a96f3885f3c2289c41b").unwrap(), + fid: 1, + r#type: 1, + }) + }; + let result = transfer.verify_signature(); + assert!(result.is_ok()); + assert!(!result.unwrap()); + } + + #[test] + fn test_fname_transfer_verify_invalid_signature_fails() { + let transfer = &FnameTransfer{ + id: 1, + from_fid: 1, + proof: Some(UserNameProof{ + timestamp: 1628882891, + name: "farcaster".into(), + owner: hex::decode("8773442740c17c9d0f0b87022c722f9a136206ed").unwrap(), + signature: hex::decode("181760f14eda0028e0b647ff15f45235526ced3b4ae07fcce06141b73d32960d3253776e62f761363fb8137087192047763f4af838950a96f3885f3c2289c41b").unwrap(), + fid: 1, + r#type: 1, + }) + }; + let result = transfer.verify_signature(); + assert!(result.is_err()); + assert_eq!(result.unwrap_err().message, "invalid signature length"); + } +} diff --git a/src/storage/store/engine.rs b/src/storage/store/engine.rs index 02a42681..bfd245d4 100644 --- a/src/storage/store/engine.rs +++ b/src/storage/store/engine.rs @@ -444,11 +444,17 @@ impl ShardEngine { "Fname transfer has no proof" ); } - let proof = fname_transfer.proof.as_ref().unwrap(); - // TODO: Verify the EIP-712 server signature + + match fname_transfer.verify_signature() { + Ok(_) => {} + Err(err) => { + warn!("Error validating fname transfer: {:?}", err); + } + } + let event = UserDataStore::merge_username_proof( &self.stores.user_data_store, - proof, + fname_transfer.proof.as_ref().unwrap(), txn_batch, ); match event {