use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MembershipProof {
pub registry_root: Vec<u8>,
pub merkle_path: Vec<Vec<u8>>,
pub domain_tag: String,
pub verified: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AnonymousMembershipProof {
pub nullifier: Vec<u8>,
pub group_root: Vec<u8>,
pub proof_bytes: Vec<u8>,
pub domain_tag: String,
pub verified: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PrivateOwnershipResult {
pub is_owner: bool,
pub proof: Option<MembershipProof>,
pub error: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CircleMembershipVerification {
pub is_member: bool,
pub proof: Option<AnonymousMembershipProof>,
pub error: Option<String>,
}
pub fn validate_membership_proof(proof: &MembershipProof) -> Result<(), String> {
if proof.registry_root.is_empty() {
return Err("Empty registry root".to_string());
}
if proof.registry_root.len() != 32 {
return Err("Registry root must be 32 bytes".to_string());
}
if !proof.domain_tag.starts_with("ZTML:") {
return Err("Invalid domain tag".to_string());
}
Ok(())
}
pub fn validate_anonymous_proof(proof: &AnonymousMembershipProof) -> Result<(), String> {
if proof.nullifier.is_empty() || proof.nullifier.len() != 32 {
return Err("Nullifier must be 32 bytes".to_string());
}
if proof.group_root.is_empty() || proof.group_root.len() != 32 {
return Err("Group root must be 32 bytes".to_string());
}
if !proof.domain_tag.starts_with("ZTML:") {
return Err("Invalid domain tag".to_string());
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ownership_result_hides_identity() {
let result = PrivateOwnershipResult {
is_owner: true,
proof: Some(MembershipProof {
registry_root: vec![0xAA; 32],
merkle_path: vec![vec![0xBB; 32], vec![0xCC; 32]],
domain_tag: "ZTML:Commons:PropertyOwnership:v1".to_string(),
verified: true,
}),
error: None,
};
assert!(result.is_owner);
let json = serde_json::to_string(&result).unwrap();
assert!(!json.contains("owner_did"));
assert!(!json.contains("did:"));
}
#[test]
fn test_circle_membership_anonymous() {
let result = CircleMembershipVerification {
is_member: true,
proof: Some(AnonymousMembershipProof {
nullifier: vec![0xDD; 32],
group_root: vec![0xEE; 32],
proof_bytes: vec![1, 2, 3],
domain_tag: "ZTML:Hearth:CircleMembership:v1".to_string(),
verified: true,
}),
error: None,
};
let json = serde_json::to_string(&result).unwrap();
assert!(!json.contains("AgentPubKey"));
assert!(!json.contains("member_pubkey"));
assert!(!json.contains("member_did"));
}
#[test]
fn test_validation() {
let valid = MembershipProof {
registry_root: vec![0xAA; 32],
merkle_path: vec![],
domain_tag: "ZTML:Test:v1".to_string(),
verified: true,
};
assert!(validate_membership_proof(&valid).is_ok());
let bad_root = MembershipProof {
registry_root: vec![0xAA; 16], ..valid.clone()
};
assert!(validate_membership_proof(&bad_root).is_err());
}
}