feat(fof-layer2): CDN four-check verification on incoming FoF comments
Wires the propagation-side accept rule per docs/fof-spec/layer-2-mode2-fof-comments.md. When a BlobHeaderDiffOp:: AddComment arrives for a post whose CommentPolicy.allow_comments is FriendsOfFriends, the receive path now: 1. Looks up the parent post in storage. If the post lacks fof_gating, drop (policy says FoF but no key material to verify against). 2. Calls fof::verify_fof_group_sig (which folds together: valid pub_x_index range + Ed25519 verify of group_sig against pub_post_set[pub_x_index] over the binding tuple). 3. Checks pub_post_set[pub_x_index] is NOT in fof_gating.revocation_list (initially empty; revocation diffs land in a future slice but the check is in place now). 4. Continues to the existing identity_sig verify step. Any failure → continue (drop, don't store, don't forward). This kills the bandwidth-amplification DoS that a single admitted FoF member could otherwise mount by spamming forged group_sigs. Receive-side storage of FoF comments is via the existing storage.store_comment call; the InlineComment shape carries the FoF fields (pub_x_index, group_sig, encrypted_payload) through unchanged. 139 tests pass (relay_cooldown flake is pre-existing and unrelated). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
00522f4c4b
commit
63ff5ad6eb
1 changed files with 42 additions and 4 deletions
|
|
@ -6172,10 +6172,48 @@ impl ConnectionManager {
|
||||||
}
|
}
|
||||||
crate::types::CommentPermission::Public => {}
|
crate::types::CommentPermission::Public => {}
|
||||||
crate::types::CommentPermission::FriendsOfFriends => {
|
crate::types::CommentPermission::FriendsOfFriends => {
|
||||||
// FoF four-check verification gate lives
|
// FoF Layer 2 CDN four-check accept rule:
|
||||||
// in a future slice; for now treat as
|
// 1. parent post must carry fof_gating
|
||||||
// "drop until verified" (safest default).
|
// (otherwise the policy is ambient
|
||||||
continue;
|
// 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(
|
if !crate::crypto::verify_comment_signature(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue