Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support fname signature verifications #173

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 24 additions & 21 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
153 changes: 153 additions & 0 deletions src/core/message.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -40,3 +45,151 @@ impl proto::ValidatorMessage {
0
}
}

impl proto::FnameTransfer {
pub fn verify_signature(&self) -> Result<bool, HubError> {
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!({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should make this a const inside validations.ts

"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::<TypedData>(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");
}
}
12 changes: 9 additions & 3 deletions src/storage/store/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also do the same thing here https://github.com/farcasterxyz/snapchain-v0/blob/f5f1aff56fc11f9fb8421e6eeab9277628e35998/src/storage/store/engine.rs#L853-L855 for username proof claim signatures (I just looked and looks like we never call verifyUserNameProofClaim in hubs, this might be a bug?) and the eth verified addresses.

Could you also implement sol verified addresses (maybe in a separate PR? upto you) before moving on to the other validations?

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 {
Expand Down