//! Reference vectors pinning every verification state (PLAN.md step 3). //! //! Cases are built from fixed seeds; Ed25519 signing is deterministic, so a //! rebuilt case is byte-identical to `vectors.json`. The regular tests pin //! both directions — the JSON must verify to its recorded states, and the //! code must regenerate exactly the JSON's feeds. `write_vectors` //! regenerates the file after an intentional format change: //! `cargo test -- --ignored write_vectors`. use fumi_core::account::{identity_seed, Identity}; use fumi_core::crypto::b32; use smolboid_core::feed::{Feed, MailLine}; use smolboid_core::verify::{reply_to_body, repost_body, signed_bytes, verify, State}; use smolboid_core::time; /// Distinct fixed masters: 0xA0/0xA1 are the daemon's identity keys across a /// rotation, 0xB0 a foreign instance, 0x5B the author. const HOST0: u8 = 0xA0; const HOST1: u8 = 0xA1; const FOREIGN: u8 = 0xB0; const AUTHOR: u8 = 0x5B; const EPK: [u8; 32] = [0xE7; 32]; fn identity(master: u8, index: u32) -> Identity { Identity::from_seed(identity_seed(&[master; 32], index)) } /// Signs a record the way smolmail seals it, so the feed carries the very /// signature that sealed the mail (PLAN.md, Signing). fn sign(sender: &Identity, to: [u8; 32], unix: i64, body: &str) -> MailLine { let sender_pk = sender.pk(); MailLine { sender: sender_pk, epk: EPK, sig: sender.sign(&signed_bytes(&to, &EPK, &sender_pk, unix, body.as_bytes())), } } fn post(sender: &Identity, to: [u8; 32], unix: i64, text: &str) -> String { let mail = sign(sender, to, unix, text); // The bare form of a post. format!( "{}\t{}\n{}\n", time::render(unix), text, mail.render() ) } fn post_with_reply_to(sender: &Identity, to: [u8; 32], unix: i64, reply_to: &str, text: &str) -> String { let mail = sign(sender, to, unix, &reply_to_body(reply_to, text)); format!( "{}\t{}\n{}\n", time::render(unix), text, mail.render() ) } fn repost(sender: &Identity, to: [u8; 32], unix: i64, args: &str) -> String { let mail = sign(sender, to, unix, &repost_body(args, None)); format!( "# smol:repost {} {}\n{}\n", time::render(unix), args, mail.render() ) } fn repost_with_reply_to(sender: &Identity, to: [u8; 32], unix: i64, reply_to: &str, args: &str) -> String { let mail = sign(sender, to, unix, &repost_body(args, Some(reply_to))); format!( "# smol:repost {} {}\n{}\n", time::render(unix), args, mail.render() ) } struct Case { name: &'static str, feed: String, legacy: bool, states: Vec, } fn metadata(host_keys: &[[u8; 32]]) -> String { let mut meta = String::from( "# nick = tobias\n# url = https://smol.place/twtxt/tobias.txt\n# smol:author = tobias@smol.place\n", ); for key in host_keys { meta.push_str(&format!("# smol:host_key = {}\n", b32(key))); } meta } fn cases() -> Vec { let host0 = identity(HOST0, 0).pk(); let host1 = identity(HOST1, 0).pk(); let foreign = identity(FOREIGN, 0).pk(); let author = identity(AUTHOR, 0); let rotated = identity(AUTHOR, 1); let t1: i64 = 1_760_000_000; let t2 = t1 + 3600; let t3 = t2 + 3600; let meta1 = metadata(&[host0]); vec![ Case { name: "verified_post", feed: format!("{}{}", meta1, post(&author, host0, t1, "hello smolverse")), legacy: false, states: vec![State::Verified], }, Case { name: "unsigned_post", feed: format!( "{}{}\t{}\n", meta1, time::render(t1), "no signature line" ), legacy: false, states: vec![State::Unsigned], }, Case { name: "invalid_tampered_text", // Signed over "hello smolverse", published as "hello sm0lverse". feed: format!( "{}{}\t{}\n{}\n", meta1, time::render(t1), "hello sm0lverse", sign(&author, host0, t1, "hello smolverse").render() ), legacy: false, states: vec![State::Invalid], }, Case { name: "invalid_foreign_host", // Sealed to another instance: no host_key matches, so the record // cannot be laundered into this feed. feed: format!( "{}{}", meta1, post(&author, foreign, t1, "sealed elsewhere") ), legacy: false, states: vec![State::Invalid], }, Case { name: "invalid_bad_timestamp", feed: format!( "{}{}\t{}\n{}\n", meta1, "2026-13-40T99:99:99Z", "unparseable time", sign(&author, host0, t1, "unparseable time").render() ), legacy: false, states: vec![State::Invalid], }, Case { name: "legacy_feed", // No smol:author and no host_key: plain twtxt, records not judged. feed: format!( "# nick = tobias\n{}\t{}\n", time::render(t1), "just a legacy twt" ), legacy: true, states: vec![State::Unsigned], }, Case { name: "orphan_mail_line", feed: format!( "{}{}{}", meta1, post(&author, host0, t1, "with a signature"), format!( "# smol:mail {} {} {}\n", b32(&author.pk()), b32(&EPK), b32(&author.sign(&signed_bytes( &host0, &EPK, &author.pk(), t2, "orphaned".as_bytes() ))) ) ), legacy: false, // The second mail line belongs to no record and is ignored. states: vec![State::Verified], }, Case { name: "host_rotation", // Both eras verify because each host_key is tried in turn. feed: format!( "{}{}{}", metadata(&[host0, host1]), post(&author, host0, t1, "before the rotation"), post(&author, host1, t2, "after the rotation") ), legacy: false, states: vec![State::Verified, State::Verified], }, Case { name: "author_rotation", // Per-record sender keys: old posts stay valid under the old key. feed: format!( "{}{}{}", meta1, post(&author, host0, t1, "identity index 0"), post(&rotated, host0, t2, "identity index 1") ), legacy: false, states: vec![State::Verified, State::Verified], }, Case { name: "verified_repost", feed: format!( "{}{}", meta1, repost( &author, host0, t1, "@ #abc1234" ) ), legacy: false, states: vec![State::Verified], }, Case { name: "invalid_repost_tampered", feed: format!( "{}{}", meta1, repost( &author, host0, t1, "@ #abc1234" ) .replace("#abc1234", "#fedcba9") // signed over #abc1234 ), legacy: false, states: vec![State::Invalid], }, Case { name: "dashed_post", // Text beginning with `---` is sent raw: a single line can never // open a §5.5 frontmatter block, so nothing is escaped. feed: format!( "{}{}", meta1, post(&author, host0, t1, "--- dashed opening") ), legacy: false, states: vec![State::Verified], }, Case { name: "reply_to_post", // An ordinary send carries a signed Reply-To block; the verifier // derives the same value from the author address and sender key. feed: format!( "{}{}", meta1, post_with_reply_to( &author, host0, t1, &format!("smol://tobias@smol.place/{}", b32(&author.pk())), "hello from an ordinary send" ) ), legacy: false, states: vec![State::Verified], }, Case { name: "reply_to_key_mismatch_is_forged", // The signature covers a Reply-To naming a different key than // the record's sender, so no derived form reconstructs. feed: format!( "{}{}", meta1, post_with_reply_to( &author, host0, t1, &format!("smol://tobias@smol.place/{}", b32(&[0xEE; 32])), "signed with a foreign reply-to" ) ), legacy: false, states: vec![State::Invalid], }, Case { name: "repost_with_reply_to", feed: format!( "{}{}", meta1, repost_with_reply_to( &author, host0, t1, &format!("smol://tobias@smol.place/{}", b32(&author.pk())), "@ #abc1234" ) ), legacy: false, states: vec![State::Verified], }, Case { name: "mixed_feed", feed: format!( "{}{}\t{}\n{}{}{}", meta1, time::render(t1), "unsigned legacy line", post(&author, host0, t2, "signed after it"), repost(&author, host0, t3, "@ #hash123"), post(&author, foreign, t1 + 7200, "signed to a foreign host") ), legacy: false, states: vec![ State::Unsigned, State::Verified, State::Verified, State::Invalid, ], }, ] } #[test] fn cases_verify_to_their_expected_states() { for case in cases() { let report = verify(&Feed::parse(&case.feed)); assert_eq!(report.legacy, case.legacy, "{}: legacy flag", case.name); assert_eq!(report.states, case.states, "{}: states", case.name); } } #[test] fn parse_render_round_trips_every_case() { for case in cases() { assert_eq!(Feed::parse(&case.feed).render(), case.feed, "{}", case.name); } } #[test] fn vectors_json_pins_the_generated_cases() { let path = concat!(env!("CARGO_MANIFEST_DIR"), "/vectors.json"); let pinned: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(path).expect("vectors.json present")) .expect("vectors.json parses"); let pinned_cases = pinned["cases"].as_array().expect("cases array"); let generated = cases(); assert_eq!( pinned_cases.len(), generated.len(), "vectors.json and the code disagree on the case set" ); for (pinned, case) in pinned_cases.iter().zip(&generated) { assert_eq!(pinned["name"], case.name, "case order"); assert_eq!(pinned["feed"], case.feed, "{}: feed bytes", case.name); assert_eq!(pinned["legacy"], case.legacy, "{}: legacy", case.name); let states: Vec<&str> = pinned["states"] .as_array() .unwrap() .iter() .map(|s| s.as_str().unwrap()) .collect(); let expected: Vec<&str> = case.states.iter().map(State::as_str).collect(); assert_eq!(states, expected, "{}: states", case.name); // The pinned feed must verify exactly as recorded. let report = verify(&Feed::parse(case.feed.as_str())); assert_eq!(report.legacy, case.legacy); assert_eq!( report .states .iter() .map(State::as_str) .collect::>(), states ); } } #[test] #[ignore = "regenerates core/vectors.json after an intentional format change"] fn write_vectors() { let cases: Vec = cases() .iter() .map(|case| { serde_json::json!({ "name": case.name, "feed": case.feed, "legacy": case.legacy, "states": case.states.iter().map(State::as_str).collect::>(), }) }) .collect(); let doc = serde_json::json!({ "comment": "smolboid-core verification vectors; regenerate with: cargo test -- --ignored write_vectors", "cases": cases, }); let path = concat!(env!("CARGO_MANIFEST_DIR"), "/vectors.json"); std::fs::write(path, serde_json::to_string_pretty(&doc).unwrap() + "\n").unwrap(); }