A library for ATProtocol identities.
1//! Signature verification for AT Protocol attestations.
2//!
3//! This module provides verification functions for AT Protocol record attestations.
4
5use crate::cid::create_attestation_cid;
6use crate::errors::AttestationError;
7use crate::input::AnyInput;
8use crate::utils::BASE64;
9use atproto_identity::key::{KeyResolver, validate};
10use atproto_record::lexicon::com::atproto::repo::STRONG_REF_NSID;
11use base64::Engine;
12use serde::Serialize;
13use serde_json::{Value, Map};
14use std::convert::TryInto;
15
16/// Helper function to extract and validate signatures array from a record
17fn extract_signatures(record_object: &Map<String, Value>) -> Result<Vec<Value>, AttestationError> {
18 match record_object.get("signatures") {
19 Some(value) => value
20 .as_array()
21 .ok_or(AttestationError::SignaturesFieldInvalid)
22 .cloned(),
23 None => Ok(vec![]),
24 }
25}
26
27/// Verify all signatures in a record with flexible input types.
28///
29/// This is a high-level verification function that accepts records in multiple formats
30/// (String, Json, or TypedLexicon) and verifies all signatures with custom resolvers.
31///
32/// # Arguments
33///
34/// * `verify_input` - The record to verify (as AnyInput: String, Json, or TypedLexicon)
35/// * `repository` - The repository DID to validate against (prevents replay attacks)
36/// * `key_resolver` - Resolver for looking up verification keys from DIDs
37/// * `record_resolver` - Resolver for fetching remote attestation proof records
38///
39/// # Returns
40///
41/// Returns `Ok(())` if all signatures are valid, or an error if any verification fails.
42///
43/// # Errors
44///
45/// Returns an error if:
46/// - The input is not a valid record object
47/// - Any signature verification fails
48/// - Key or record resolution fails
49///
50/// # Type Parameters
51///
52/// * `R` - The record type (must implement Serialize + LexiconType + PartialEq + Clone)
53/// * `RR` - The record resolver type (must implement RecordResolver)
54/// * `KR` - The key resolver type (must implement KeyResolver)
55pub async fn verify_record<R, RR, KR>(
56 verify_input: AnyInput<R>,
57 repository: &str,
58 key_resolver: KR,
59 record_resolver: RR,
60) -> Result<(), AttestationError>
61where
62 R: Serialize + Clone,
63 RR: atproto_client::record_resolver::RecordResolver,
64 KR: KeyResolver,
65{
66 let record_object: Map<String, Value> = verify_input
67 .clone()
68 .try_into()
69 .map_err(|_| AttestationError::RecordMustBeObject)?;
70
71 let signatures = extract_signatures(&record_object)?;
72
73 if signatures.is_empty() {
74 return Ok(());
75 }
76
77 for signature in signatures {
78 let signature_refernce_type = signature
79 .get("$type")
80 .and_then(Value::as_str)
81 .filter(|value| !value.is_empty())
82 .ok_or(AttestationError::SigMetadataMissingType)?;
83
84 let metadata = if signature_refernce_type == STRONG_REF_NSID {
85 let aturi = signature
86 .get("uri")
87 .and_then(Value::as_str)
88 .filter(|value| !value.is_empty())
89 .ok_or(AttestationError::SignatureMissingField {
90 field: "uri".to_string(),
91 })?;
92
93 record_resolver
94 .resolve::<serde_json::Value>(aturi)
95 .await
96 .map_err(|error| AttestationError::RemoteAttestationFetchFailed {
97 uri: aturi.to_string(),
98 error,
99 })?
100 } else {
101 signature.clone()
102 };
103
104 let computed_cid = create_attestation_cid(
105 verify_input.clone(),
106 AnyInput::Serialize(metadata.clone()),
107 repository,
108 )?;
109
110 if signature_refernce_type == STRONG_REF_NSID {
111 let attestation_cid = metadata
112 .get("cid")
113 .and_then(Value::as_str)
114 .filter(|value| !value.is_empty())
115 .ok_or(AttestationError::SignatureMissingField {
116 field: "cid".to_string(),
117 })?;
118
119 if computed_cid.to_string() != attestation_cid {
120 return Err(AttestationError::RemoteAttestationCidMismatch {
121 expected: attestation_cid.to_string(),
122 actual: computed_cid.to_string(),
123 });
124 }
125 continue;
126 }
127
128 let key = metadata
129 .get("key")
130 .and_then(Value::as_str)
131 .filter(|value| !value.is_empty())
132 .ok_or(AttestationError::SignatureMissingField {
133 field: "key".to_string(),
134 })?;
135 let key_data = key_resolver.resolve(key).await.map_err(|error| {
136 AttestationError::KeyResolutionFailed {
137 key: key.to_string(),
138 error,
139 }
140 })?;
141
142 let signature_bytes = metadata
143 .get("signature")
144 .and_then(Value::as_object)
145 .and_then(|object| object.get("$bytes"))
146 .and_then(Value::as_str)
147 .ok_or(AttestationError::SignatureBytesFormatInvalid)?;
148
149 let signature_bytes = BASE64
150 .decode(signature_bytes)
151 .map_err(|error| AttestationError::SignatureDecodingFailed { error })?;
152
153 let computed_cid_bytes = computed_cid.to_bytes();
154
155 validate(&key_data, &signature_bytes, &computed_cid_bytes)
156 .map_err(|error| AttestationError::SignatureValidationFailed { error })?;
157 }
158
159 Ok(())
160}