diff --git a/crates/core/src/connection.rs b/crates/core/src/connection.rs index e7b1fb5..2c27307 100644 --- a/crates/core/src/connection.rs +++ b/crates/core/src/connection.rs @@ -6172,10 +6172,48 @@ impl ConnectionManager { } crate::types::CommentPermission::Public => {} crate::types::CommentPermission::FriendsOfFriends => { - // FoF four-check verification gate lives - // in a future slice; for now treat as - // "drop until verified" (safest default). - continue; + // FoF Layer 2 CDN four-check accept rule: + // 1. parent post must carry fof_gating + // (otherwise the policy is ambient + // with no key material to verify); + // 2. pub_x_index must point at a real + // entry in pub_post_set; + // 3. group_sig must validate against + // pub_post_set[pub_x_index]; + // 4. revocation_list must not contain + // pub_post_set[pub_x_index]; + // 5. identity_sig (existing comment + // signature field) verified below. + // + // Failures drop the comment without + // forwarding — kills the bandwidth-DoS + // attack an admitted-but-malicious FoF + // member could otherwise mount. + let parent = match storage.get_post(&payload.post_id) { + Ok(Some(p)) => p, + _ => continue, + }; + let Some(gating) = parent.fof_gating.as_ref() else { continue; }; + if !crate::fof::verify_fof_group_sig(comment, gating) { + continue; + } + // Revocation check (step 4). The + // revocation_list on the post's stored + // copy is the on-publish snapshot; + // revocation diffs that arrive later + // are applied against the local + // BlobHeader copy (separate slice). + if let Some(idx) = comment.pub_x_index { + let pub_x = gating.pub_post_set + .get(idx as usize) + .copied(); + if let Some(pub_x) = pub_x { + if gating.revocation_list.iter() + .any(|r| r.revoked_pub_x == pub_x) { + continue; + } + } + } } } if !crate::crypto::verify_comment_signature(