use crate::stored_event::event_kind;
use nostr_sdk::prelude::*;
const TAG_OWNER: &str = "vco";
pub fn build_owner_attestation_unsigned(
owner_pubkey: PublicKey,
community_id: &str,
) -> UnsignedEvent {
EventBuilder::new(Kind::Custom(event_kind::APPLICATION_SPECIFIC), "")
.tags([Tag::custom(
TagKind::Custom(TAG_OWNER.into()),
[community_id.to_string()],
)])
.build(owner_pubkey)
}
pub fn verify_owner_attestation(
attestation_json: &str,
community_id: &str,
) -> Option<PublicKey> {
let ev: Event = serde_json::from_str(attestation_json).ok()?;
ev.verify().ok()?; let bound = ev.tags.iter().find_map(|t| {
let s = t.as_slice();
(s.len() >= 2 && s[0] == TAG_OWNER).then(|| s[1].clone())
})?;
(bound == community_id).then_some(ev.pubkey)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn attestation_round_trips_binds_and_rejects_forgery() {
let owner = Keys::generate();
let cid = "a".repeat(64);
let signed = build_owner_attestation_unsigned(owner.public_key(), &cid)
.sign_with_keys(&owner)
.unwrap();
let json = signed.as_json();
assert_eq!(verify_owner_attestation(&json, &cid), Some(owner.public_key()));
assert_eq!(verify_owner_attestation(&json, &"b".repeat(64)), None);
assert_eq!(verify_owner_attestation("not json", &cid), None);
let mallory = Keys::generate();
let forged = build_owner_attestation_unsigned(mallory.public_key(), &cid)
.sign_with_keys(&mallory)
.unwrap()
.as_json();
assert_eq!(verify_owner_attestation(&forged, &cid), Some(mallory.public_key()));
assert_ne!(verify_owner_attestation(&forged, &cid), Some(owner.public_key()));
}
#[test]
fn attestation_tag_layout_is_frozen() {
let owner = Keys::generate();
let cid = "c".repeat(64);
let unsigned = build_owner_attestation_unsigned(owner.public_key(), &cid);
assert_eq!(unsigned.content, "");
let tags: Vec<Vec<String>> = unsigned.tags.iter().map(|t| t.as_slice().to_vec()).collect();
assert_eq!(tags, vec![vec!["vco".to_string(), cid]], "exactly one [\"vco\", community_id] tag");
}
}