//! `wai.telemetry.aggregate` (extensions/telemetry-aggregation.md): the committed corpus //! `live-conformance/aggregate/vectors.json` regenerates byte for byte from its fixed seeds, the //! Prio3 test vectors of `draft-irtf-cfrg-vdaf-22` vendored beside it pass and are the files //! as published, every receipt, cell and delivery-only report gets the outcome it states, every //! reason the extension adds refuses a vector, the room's aggregated cell is the plaintext cell, //! the extension's §11 lists exactly the reasons the reference reports, and delivery-telemetry //! §3.4 classes exactly the keys the reference classes. #![cfg(feature = "telemetry_aggregate")] use std::collections::BTreeSet; use std::path::PathBuf; use sha2::{Digest, Sha256}; use wai::telemetry::cmcd::key_class; use wai::telemetry_aggregate::{corpus, vdaf_vectors, Reason}; fn wai_dir() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..") } fn read(p: &str) -> String { std::fs::read_to_string(wai_dir().join(p)).unwrap_or_else(|e| panic!("{p}: {e}")) } fn committed() -> String { read("live-conformance/aggregate/vectors.json") } #[test] fn the_committed_corpus_regenerates_from_its_seeds() { assert!(committed() == corpus::generate(), "live-conformance/aggregate/vectors.json differs from `wai_telemetry_aggregate_vectors gen`"); let mut files: Vec = std::fs::read_dir(wai_dir().join("live-conformance/aggregate")).unwrap().flatten().map(|e| e.file_name().to_string_lossy().into_owned()).collect(); files.sort(); assert_eq!(files, ["README.md", "vdaf-22", "vectors.json"]); } /// The draft's vectors, as published at the commit its [TestVectors] reference names: each /// file's SHA-256, so that an edit to one is seen. const VDAF_FILES: [(&str, &str); 20] = [ ("Prio3Count_0.json", "0701925266d136d90df37dc7e447d91c885ecf2e26ba62ec98811283fd56a4d3"), ("Prio3Count_1.json", "801b983009c1d262a5bf47154b25007392d865810f38a9da5733addd58c57eb7"), ("Prio3Count_2.json", "67d5535501d5be79ae06766cd9e94cce8e83fb54a7925ff3a0404338064c8a59"), ("Prio3Count_bad_gadget_poly.json", "e060c4e267342a84f09b3a27331ac713fff38547f1228c239199cb173059981d"), ("Prio3Count_bad_helper_seed.json", "b6181bc26554796048fe91c7e2405dd084fadfab1fbc5b531948b46a02ea4e4f"), ("Prio3Count_bad_meas_share.json", "29fe7518bb85c270783198e924eedcb981267b84577d7dd8b0c4de5fe478efdc"), ("Prio3Count_bad_wire_seed.json", "4d593e7e5128b8b439b51743ffe74fcc9cfa41d487cefe35da29dde89eb85875"), ("Prio3Histogram_0.json", "991bafe94fe1725fb4954395cc94d975526626e812ed19db88f120eed5efdd2c"), ("Prio3Histogram_1.json", "65225dc0b8dff810e8a61630db401ccb82b04b58dc75ae7410ab28327353fc4a"), ("Prio3Histogram_2.json", "f022b9d68ad4612c8b9270afedcb8e3d86b4e039902c4e90a2ce85278d9a05af"), ("Prio3Histogram_bad_helper_jr_blind.json", "8c8f4362444ce0a634136c05c0e4466df81679a8d6a5a34f7fd6d775c170b8a8"), ("Prio3Histogram_bad_leader_jr_blind.json", "cb1c86be8ca9a08692eaae67a8dc1b590f5a85c66eb7bcc09da43a0c3a6ba0b2"), ("Prio3Histogram_bad_public_share.json", "b27f48e8157a3da7c5fce1f523899931c861f94562096ca5c57299a0d48a2b4e"), ("Prio3Histogram_bad_verifier_message.json", "b91124fe933c12deb1c62d251cf795f93a6dccf765bc9d6af5390f3cb5e7820b"), ("Prio3SumVec_0.json", "93a4ae011b547a0915a6099e731660058f1140c537af71e2f0100e61ffa82f9c"), ("Prio3SumVec_1.json", "cfaf8bf26280c399026009be710d0f196fb48d9c44d2bc7094df7ff593fbd588"), ("Prio3Sum_0.json", "07c448e3690c8392a8d89c2cc0c298b876f4e59f1220cc49c5b0b28d43bc7965"), ("Prio3Sum_1.json", "2b4239d0c5e2ba8f9c64f3e51dbcc3fcd1fb7d4f21cdd352d42d4916828e2808"), ("Prio3Sum_2.json", "4d1b5cd33a5aaee952dc1e8aecc857a1926f92db8d4fdc7031cb032a69ee8ebd"), ("XofTurboShake128.json", "8f34d9dd964e5320b8db214706dcbf4bf5da4cba95d11a99bd25e73658c6e0ce"), ]; #[test] fn the_drafts_prio3_vectors_are_as_published_and_pass() { let dir = wai_dir().join("live-conformance/aggregate/vdaf-22"); let (names, fails) = vdaf_vectors::run_dir(&dir); let want: Vec = VDAF_FILES.iter().map(|(n, _)| n.to_string()).collect(); assert_eq!(names, want); for (n, h) in VDAF_FILES { let got: String = Sha256::digest(std::fs::read(dir.join(n)).unwrap()).iter().map(|b| format!("{b:02x}")).collect(); assert_eq!(got, h, "{n} differs from the file as published"); } assert!(fails.is_empty(), "{}", fails.join("\n")); } #[test] fn every_receipt_cell_and_delivery_only_report_gets_its_stated_outcome() { let fails = corpus::verify(&committed()); assert!(fails.is_empty(), "{}", fails.join("\n")); } /// Every reason this extension adds, and every code of another text it reuses, refuses a /// vector; the accepted vectors are §12's. #[test] fn the_corpus_covers_every_reason_and_case() { let doc: serde_json::Value = serde_json::from_str(&committed()).unwrap(); let mut seen: BTreeSet = BTreeSet::new(); for section in ["receipts", "cells", "delivery_only"] { seen.extend(doc[section].as_array().unwrap().iter().filter_map(|v| v["outcome"]["reason"].as_str().map(String::from))); } for f in doc["flows"].as_array().unwrap() { seen.extend(f["job"]["outcomes"].as_array().unwrap().iter().map(|o| o["outcome"].as_str().unwrap().to_string())); seen.extend(f["upload_errors"].as_array().unwrap().iter().map(|o| o["error"].as_str().unwrap().to_string())); seen.extend(f["refusals"].as_array().unwrap().iter().map(|o| o["outcome"].as_str().unwrap().to_string())); } for r in Reason::ALL { assert!(seen.contains(r.code()), "nothing in the corpus is refused {r}"); } for r in Reason::REUSED { assert!(seen.contains(r), "nothing in the corpus is refused {r}"); } let accepted: Vec<&str> = doc["receipts"].as_array().unwrap().iter().filter(|v| v["outcome"]["outcome"] == "accept").map(|v| v["name"].as_str().unwrap()).collect(); assert_eq!(accepted, ["outcomes", "stalls", "durations", "start_time"]); for v in doc["receipts"].as_array().unwrap().iter().take(4) { assert!(v["outcome"]["report_count"].as_u64().unwrap() >= 50, "an accepted batch holds at least fifty reports"); } let cell = &doc["cells"][0]; assert_eq!(cell["outcome"]["outcome"], "accept"); assert_eq!(cell["outcome"]["cell"], cell["plaintext"], "the room's aggregated cell is the plaintext cell"); for f in doc["flows"].as_array().unwrap() { let rejected: Vec<&str> = f["job"]["outcomes"].as_array().unwrap().iter().map(|o| o["outcome"].as_str().unwrap()).filter(|o| *o != "committed").collect(); assert!(rejected.contains(&"vdaf_verify_error"), "{}: the out-of-range report's proof fails", f["name"]); } } /// §11 of the extension lists exactly the codes `Reason::ALL` has, each once, in order. #[test] fn the_extension_lists_the_reasons() { let text = read("extensions/telemetry-aggregation.md"); let at = text.find("\n## 11. Reasons").expect("a §11"); let end = text[at + 1..].find("\n## ").map_or(text.len(), |e| at + 1 + e); let listed: Vec = text[at..end].lines().filter(|l| l.starts_with("| `")).filter_map(|l| l.split('`').nth(1).map(String::from)).collect(); let want: Vec = Reason::ALL.iter().map(|r| r.code().to_string()).collect(); assert_eq!(listed, want, "§11's table and Reason::ALL differ"); } /// delivery-telemetry §3.4's table states the class of every key §3 and §4 write, as /// `key_class` does, and no other. #[test] fn delivery_telemetry_classes_the_keys_the_reference_does() { let text = read("extensions/delivery-telemetry.md"); let at = text.find("\n§3.4").expect("a §3.4"); let end = text[at..].find("\n## ").map_or(text.len(), |e| at + e); let mut n = 0; for l in text[at..end].lines().filter(|l| l.starts_with("| `")) { let cells: Vec<&str> = l.split('|').map(str::trim).collect(); let class = cells[2]; for k in cells[1].split(", ").map(|k| k.trim_matches('`')) { let key = if k.starts_with("wai-") { format!("science.transaction.{k}") } else { k.to_string() }; assert_eq!(key_class(&key).map(|c| c.name()), Some(class), "§3.4 classes `{k}` {class}"); n += 1; } } let mapped = ["v", "sid", "sn", "cid", "st", "sf", "ot", "d", "dl", "msd", "bsa", "bsda", "dfa", "ts", "e", "sta", "rc", "ttfb", "ttlb", "url", "cen"]; let custom = ["b", "tx", "ch", "cap", "ri", "l", "lt", "p", "rcpt", "lc", "lcf", "coh", "u", "pl", "late", "dec", "sw", "co", "cr", "cf", "clm"]; assert_eq!(n, mapped.len() + custom.len(), "§3.4 classes every key §3 and §4 write, once"); }