1use serde::Serialize;
32
33use crate::{
34 DataIntegrityError, DataIntegrityProof, SignOptions, VerificationMethodResolver, VerifyOptions,
35 signer::Signer,
36};
37
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56#[non_exhaustive]
57pub enum VerifyPolicy {
58 RequireAll,
60 RequireAny,
62 RequireThreshold(usize),
68}
69
70#[derive(Debug)]
72#[non_exhaustive]
73pub struct MultiVerifyResult<'a> {
74 pub passed: Vec<&'a DataIntegrityProof>,
76 pub failed: Vec<(&'a DataIntegrityProof, DataIntegrityError)>,
79 pub policy_satisfied: bool,
81}
82
83impl MultiVerifyResult<'_> {
84 pub fn into_result(self) -> Result<(), DataIntegrityError> {
88 if self.policy_satisfied {
89 Ok(())
90 } else {
91 Err(DataIntegrityError::Conformance(format!(
92 "multi-proof policy not satisfied ({} passed, {} failed)",
93 self.passed.len(),
94 self.failed.len()
95 )))
96 }
97 }
98}
99
100impl DataIntegrityProof {
101 pub async fn sign_multi<S>(
112 data_doc: &S,
113 signers: &[&dyn Signer],
114 options: SignOptions,
115 ) -> Result<Vec<DataIntegrityProof>, DataIntegrityError>
116 where
117 S: Serialize,
118 {
119 if signers.is_empty() {
120 return Err(DataIntegrityError::MalformedProof(
121 "sign_multi called with no signers".to_string(),
122 ));
123 }
124
125 let mut options = options;
131 if options.created.is_none() {
132 options.created = Some(chrono::Utc::now());
133 }
134
135 let mut proofs = Vec::with_capacity(signers.len());
136 for signer in signers {
137 let proof = DataIntegrityProof::sign(data_doc, *signer, options.clone()).await?;
138 proofs.push(proof);
139 }
140 Ok(proofs)
141 }
142}
143
144pub async fn verify_multi<'a, S, R>(
153 proofs: &'a [DataIntegrityProof],
154 data_doc: &S,
155 resolver: &R,
156 options: VerifyOptions,
157 policy: VerifyPolicy,
158) -> MultiVerifyResult<'a>
159where
160 S: Serialize + Sync,
161 R: VerificationMethodResolver + ?Sized,
162{
163 let mut passed = Vec::new();
164 let mut failed = Vec::new();
165
166 for proof in proofs {
167 match proof.verify(data_doc, resolver, options.clone()).await {
168 Ok(()) => passed.push(proof),
169 Err(e) => failed.push((proof, e)),
170 }
171 }
172
173 let policy_satisfied = match policy {
174 VerifyPolicy::RequireAll => !proofs.is_empty() && failed.is_empty(),
175 VerifyPolicy::RequireAny => !passed.is_empty(),
176 VerifyPolicy::RequireThreshold(n) => {
177 if n == 0 {
178 !proofs.is_empty() && failed.is_empty()
179 } else {
180 passed.len() >= n
181 }
182 }
183 };
184
185 MultiVerifyResult {
186 passed,
187 failed,
188 policy_satisfied,
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195 use crate::DidKeyResolver;
196 use affinidi_secrets_resolver::secrets::Secret;
197 use serde_json::json;
198
199 fn make_signer(kind: &str, seed: u8) -> Secret {
200 let secret = match kind {
201 "ed25519" => Secret::generate_ed25519(None, Some(&[seed; 32])),
202 #[cfg(feature = "ml-dsa")]
203 "ml-dsa-44" => Secret::generate_ml_dsa_44(None, Some(&[seed; 32])),
204 _ => panic!("unknown kind {kind}"),
205 };
206 let pk_mb = secret.get_public_keymultibase().unwrap();
207 let mut s = secret.clone();
208 s.id = format!("did:key:{pk_mb}#{pk_mb}");
209 s
210 }
211
212 #[tokio::test]
213 async fn sign_multi_pins_created_across_batch() {
214 let a = make_signer("ed25519", 10);
218 let b = make_signer("ed25519", 11);
219 let signers: Vec<&dyn Signer> = vec![&a, &b];
220 let doc = json!({"pin": "created"});
221 let proofs = DataIntegrityProof::sign_multi(&doc, &signers, SignOptions::new())
222 .await
223 .unwrap();
224 assert_eq!(proofs.len(), 2);
225 assert_eq!(proofs[0].created, proofs[1].created);
226 }
227
228 #[tokio::test]
229 async fn sign_multi_emits_one_proof_per_signer() {
230 let a = make_signer("ed25519", 1);
231 let b = make_signer("ed25519", 2);
232 let signers: Vec<&dyn Signer> = vec![&a, &b];
233 let doc = json!({"multi": true});
234 let proofs = DataIntegrityProof::sign_multi(&doc, &signers, SignOptions::new())
235 .await
236 .unwrap();
237 assert_eq!(proofs.len(), 2);
238 }
239
240 #[cfg(feature = "ml-dsa")]
241 #[tokio::test]
242 async fn sign_multi_hybrid_classical_and_pqc() {
243 let classical = make_signer("ed25519", 9);
246 let pqc = make_signer("ml-dsa-44", 9);
247 let signers: Vec<&dyn Signer> = vec![&classical, &pqc];
248 let doc = json!({"hybrid": "yes"});
249
250 let proofs = DataIntegrityProof::sign_multi(&doc, &signers, SignOptions::new())
251 .await
252 .unwrap();
253 assert_eq!(proofs.len(), 2);
254 assert_eq!(
255 proofs[0].cryptosuite,
256 crate::crypto_suites::CryptoSuite::EddsaJcs2022
257 );
258 assert_eq!(
259 proofs[1].cryptosuite,
260 crate::crypto_suites::CryptoSuite::MlDsa44Jcs2024
261 );
262
263 let result = verify_multi(
265 &proofs,
266 &doc,
267 &DidKeyResolver,
268 VerifyOptions::new(),
269 VerifyPolicy::RequireAll,
270 )
271 .await;
272 assert!(result.policy_satisfied);
273 assert_eq!(result.passed.len(), 2);
274 assert!(result.failed.is_empty());
275 }
276
277 #[tokio::test]
278 async fn verify_multi_require_any_tolerates_one_bad_proof() {
279 let good = make_signer("ed25519", 3);
280 let signers: Vec<&dyn Signer> = vec![&good];
281 let doc = json!({"x": 1});
282 let mut proofs = DataIntegrityProof::sign_multi(&doc, &signers, SignOptions::new())
283 .await
284 .unwrap();
285
286 let mut bad = proofs[0].clone();
288 let pv = bad.proof_value.take().unwrap();
289 let mut raw = multibase::decode(&pv).unwrap().1;
290 raw[0] ^= 0xff;
291 bad.proof_value = Some(multibase::encode(multibase::Base::Base58Btc, raw));
292 proofs.push(bad);
293
294 let result = verify_multi(
296 &proofs,
297 &doc,
298 &DidKeyResolver,
299 VerifyOptions::new(),
300 VerifyPolicy::RequireAny,
301 )
302 .await;
303 assert!(result.policy_satisfied);
304 assert_eq!(result.passed.len(), 1);
305 assert_eq!(result.failed.len(), 1);
306
307 let result = verify_multi(
309 &proofs,
310 &doc,
311 &DidKeyResolver,
312 VerifyOptions::new(),
313 VerifyPolicy::RequireAll,
314 )
315 .await;
316 assert!(!result.policy_satisfied);
317 }
318
319 #[tokio::test]
320 async fn verify_multi_threshold() {
321 let a = make_signer("ed25519", 1);
322 let b = make_signer("ed25519", 2);
323 let c = make_signer("ed25519", 3);
324 let signers: Vec<&dyn Signer> = vec![&a, &b, &c];
325 let doc = json!({"witnesses": 3});
326 let proofs = DataIntegrityProof::sign_multi(&doc, &signers, SignOptions::new())
327 .await
328 .unwrap();
329
330 let result = verify_multi(
331 &proofs,
332 &doc,
333 &DidKeyResolver,
334 VerifyOptions::new(),
335 VerifyPolicy::RequireThreshold(2),
336 )
337 .await;
338 assert!(result.policy_satisfied);
339 assert_eq!(result.passed.len(), 3);
340 }
341
342 #[tokio::test]
346 async fn verify_multi_threshold_zero_equals_require_all() {
347 let a = make_signer("ed25519", 1);
348 let signers: Vec<&dyn Signer> = vec![&a];
349 let doc = json!({"t": 0});
350 let proofs = DataIntegrityProof::sign_multi(&doc, &signers, SignOptions::new())
351 .await
352 .unwrap();
353
354 let require_all = verify_multi(
355 &proofs,
356 &doc,
357 &DidKeyResolver,
358 VerifyOptions::new(),
359 VerifyPolicy::RequireAll,
360 )
361 .await;
362 let threshold_zero = verify_multi(
363 &proofs,
364 &doc,
365 &DidKeyResolver,
366 VerifyOptions::new(),
367 VerifyPolicy::RequireThreshold(0),
368 )
369 .await;
370 assert_eq!(
371 require_all.policy_satisfied,
372 threshold_zero.policy_satisfied
373 );
374 assert_eq!(require_all.passed.len(), threshold_zero.passed.len());
375
376 let empty: Vec<DataIntegrityProof> = vec![];
378 let r = verify_multi(
379 &empty,
380 &doc,
381 &DidKeyResolver,
382 VerifyOptions::new(),
383 VerifyPolicy::RequireThreshold(0),
384 )
385 .await;
386 assert!(!r.policy_satisfied);
387 }
388
389 #[tokio::test]
390 async fn sign_multi_empty_signer_list_is_error() {
391 let doc = json!({});
392 let err = DataIntegrityProof::sign_multi(&doc, &[], SignOptions::new())
393 .await
394 .unwrap_err();
395 assert!(matches!(err, DataIntegrityError::MalformedProof(_)));
396 }
397}