diff --git a/.trinity/seals/src_port::trios::crates::trios-chat::rings::CR-CHAT-07::src::cover_traffic_replay_detection_guard.json b/.trinity/seals/src_port::trios::crates::trios-chat::rings::CR-CHAT-07::src::cover_traffic_replay_detection_guard.json new file mode 100644 index 0000000000..c30c2bb8f1 --- /dev/null +++ b/.trinity/seals/src_port::trios::crates::trios-chat::rings::CR-CHAT-07::src::cover_traffic_replay_detection_guard.json @@ -0,0 +1,28 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+01d0a635f", + "closure": [], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:5d163c948bcbd1bf56d800662594f481fcfb25c3b88fab43b58b185a21ae5f67", + "gen_hash_rust": "sha256:0431e36cfd9545d88a4cae54f41ec5781b314195ab9a3e5f4bddd7c16c09452d", + "gen_hash_verilog": "sha256:7d21d6225202df08c904c41746d605beade8fab25cbc04687146efcb3b12a28e", + "gen_hash_zig": "sha256:5e22547ca28a10b4eb0b16eb09385d6db7b59bf2cbf107384192a8ae6b47f67f", + "module": "port::trios::crates::trios-chat::rings::CR-CHAT-07::src::cover_traffic_replay_detection_guard", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-17T03:02:40Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:5f8bdbea4ad59f4e450b604c20ee40c98ad8bc78b1c192f33ff01acce365f695", + "spec_path": "specs/port/trios/crates/trios-chat/rings/CR-CHAT-07/src/cover_traffic_replay_detection_guard.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 12, + "total": 12, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/docs/reports/t27b_expectations.json b/docs/reports/t27b_expectations.json index c041cbab56..b7bf47dfe1 100644 --- a/docs/reports/t27b_expectations.json +++ b/docs/reports/t27b_expectations.json @@ -855,6 +855,7 @@ {"path": "specs/port/trios/crates/trios-chat/rings/CR-CHAT-03/src/unmerged_leaves_bound_guard.t27", "t27b": "pass"}, {"path": "specs/port/trios/crates/trios-chat/rings/CR-CHAT-05/src/envelope_size_distribution_uniformity.t27", "t27b": "pass"}, {"path": "specs/port/trios/crates/trios-chat/rings/CR-CHAT-07/src/cover_decoy_ratio_governor.t27", "t27b": "pass"}, + {"path": "specs/port/trios/crates/trios-chat/rings/CR-CHAT-07/src/cover_traffic_replay_detection_guard.t27", "t27b": "pass"}, {"path": "specs/port/trios/crates/trios-chat/rings/CR-CHAT-LAWS/src/lib.t27", "t27b": "pass"}, {"path": "specs/port/trios/crates/trios-cli/src/cmd/agent.t27", "t27b": "pass"}, {"path": "specs/port/trios/crates/trios-cli/src/cmd/commit.t27", "t27b": "pass"}, diff --git a/specs/port/trios/crates/trios-chat/rings/CR-CHAT-07/src/cover_traffic_replay_detection_guard.t27 b/specs/port/trios/crates/trios-chat/rings/CR-CHAT-07/src/cover_traffic_replay_detection_guard.t27 new file mode 100644 index 0000000000..f2e64b4d31 --- /dev/null +++ b/specs/port/trios/crates/trios-chat/rings/CR-CHAT-07/src/cover_traffic_replay_detection_guard.t27 @@ -0,0 +1,315 @@ +module port::trios::crates::trios-chat::rings::CR-CHAT-07::src::cover_traffic_replay_detection_guard; + +// Port of gHashTag/trios crates/trios-chat/rings/CR-CHAT-07/src/cover_traffic_replay_detection_guard.rs +// (CR-CHAT-07 - Cover traffic replay detection guard, Wave-113 +// Lane A, R-CHAT-10). +// +// Cover patterns must not repeat: a repeating pattern hands the +// adversary a database match that names cover epochs, patterns +// that repeat against no known schedule flag as real, and +// cross-window matches reveal the schedule's period. The +// rules, in the original's order: the batch ceiling, then per +// window the zero-index probe, the zero-hash probe, the +// emission floor, the strictly-increasing index and the +// repeated-hash scan. +// +// The original returns Result<(), CoverReplayError>. This port +// carries the verdict as a named struct (owner decision D5): Ok +// first, then the six variants in the original's declaration +// order - DuplicatePattern, NotIncreasing, ZeroIndex, ZeroHash, +// BelowMin, TooMany - with every payload field: idx for the +// zero and duplicate rules, idx with prev and current for the +// monotonic break, idx with count and min for the floor, and +// got with max for the ceiling. +// +// The BTreeSet dedup becomes a scan over the windows already +// visited; the hashes stay the fixed [32]u8 form. + +// Minimum emissions per window. +pub const CTRD_MIN_EMISSIONS: usize = 10; + +// Maximum windows per batch. +pub const CTRD_MAX_WINDOWS: usize = 256; + +// Pattern hash length. +pub const CTRD_HASH_LEN: usize = 32; + +// A cover traffic window pattern. +pub struct CoverWindow { + index: u64, + pattern_hash: [32]u8, + emission_count: usize, +} + +// Verdict codes: Ok(()) and CoverReplayError's variants, in declaration order. +pub enum CoverReplayCode { + Ok, + DuplicatePattern, + NotIncreasing, + ZeroIndex, + ZeroHash, + BelowMin, + TooMany, +} + +// The verdict of the replay validation, with every error payload +// field. +pub struct CoverReplayVerdict { + code: CoverReplayCode, + idx: usize, + got: usize, + bound: usize, + prev: u64, + current: u64, +} + +// Whole-form equality over the fixed thirty-two hash bytes. +pub fn hashes_equal(a: [32]u8, b: [32]u8) -> bool { + for i in 0..32 { + if (a[i] != b[i]) { + return false; + } + } + return true; +} + +// The all-zero hash - the original's [0u8; 32] probe. +pub fn hash_zero(h: [32]u8) -> bool { + for i in 0..32 { + if (h[i] != 0) { + return false; + } + } + return true; +} + +// Whether window i repeats an earlier hash - the original's +// BTreeSet insert probe, answered by a scan. +pub fn hash_seen_before(windows: []const CoverWindow, i: usize) -> bool { + for j in 0..i { + if (hashes_equal(windows[j].pattern_hash, windows[i].pattern_hash)) { + return true; + } + } + return false; +} + +// [VERIFIED] Validate cover traffic replay detection. Returns +// Ok only when every rule holds, checked in the original's +// order: the batch ceiling, then per window the zero index, +// the zero hash, the emission floor, the strictly-increasing +// index and the repeated hash. +pub fn validate_cover_replay(windows: []const CoverWindow) -> CoverReplayVerdict { + if (windows.len > CTRD_MAX_WINDOWS) { + return CoverReplayVerdict { code: CoverReplayCode.TooMany, idx: 0, got: windows.len, bound: CTRD_MAX_WINDOWS, prev: 0, current: 0, }; + } + var prev: u64 = 0; + for i in 0..windows.len { + if (windows[i].index == 0) { + return CoverReplayVerdict { code: CoverReplayCode.ZeroIndex, idx: i, got: 0, bound: 0, prev: 0, current: 0, }; + } + if (hash_zero(windows[i].pattern_hash)) { + return CoverReplayVerdict { code: CoverReplayCode.ZeroHash, idx: i, got: 0, bound: 0, prev: 0, current: 0, }; + } + if (windows[i].emission_count < CTRD_MIN_EMISSIONS) { + return CoverReplayVerdict { code: CoverReplayCode.BelowMin, idx: i, got: windows[i].emission_count, bound: CTRD_MIN_EMISSIONS, prev: 0, current: 0, }; + } + if (i > 0) { + if (windows[i].index <= prev) { + return CoverReplayVerdict { code: CoverReplayCode.NotIncreasing, idx: i, got: 0, bound: 0, prev: prev, current: windows[i].index, }; + } + } + if (hash_seen_before(windows, i)) { + return CoverReplayVerdict { code: CoverReplayCode.DuplicatePattern, idx: i, got: 0, bound: 0, prev: 0, current: 0, }; + } + prev = windows[i].index; + } + return CoverReplayVerdict { code: CoverReplayCode.Ok, idx: 0, got: 0, bound: 0, prev: 0, current: 0, }; +} + +// The pattern hashes of the fixtures, one byte repeated +// thirty-two times - the original's hash(byte) helper, entry by +// entry. +pub const HASH_01: [32]u8 = [32]u8{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,}; +pub const HASH_02: [32]u8 = [32]u8{2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,}; +pub const HASH_03: [32]u8 = [32]u8{3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,}; +pub const HASH_AA: [32]u8 = [32]u8{170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170,}; +pub const ZERO_HASH32: [32]u8 = [32]u8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,}; + +// The leading hash bytes of the too-many fixture: the +// original's (i as u8).wrapping_add(1) walk over 0..=256. +pub const CTRD_FLOOD_257: [257]u8 = [257]u8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 0, 1}; + +// **CTRD-01** - a duplicate pattern rejected: the second window +// repeats the first's hash. +test ctrd_01_duplicate_rejected { + const ws: [2]CoverWindow = [2]CoverWindow{ + CoverWindow { index: 1, pattern_hash: HASH_AA, emission_count: 20, }, + CoverWindow { index: 2, pattern_hash: HASH_AA, emission_count: 25, }, + }; + const v = validate_cover_replay(&ws); + assert(v.code == CoverReplayCode.DuplicatePattern); + assert(v.idx == 1); +} + +// **CTRD-02** - a non-increasing index rejected: three after +// five. +test ctrd_02_not_increasing_rejected { + const ws: [2]CoverWindow = [2]CoverWindow{ + CoverWindow { index: 5, pattern_hash: HASH_01, emission_count: 20, }, + CoverWindow { index: 3, pattern_hash: HASH_02, emission_count: 20, }, + }; + const v = validate_cover_replay(&ws); + assert(v.code == CoverReplayCode.NotIncreasing); + assert(v.idx == 1); + assert(v.prev == 5); + assert(v.current == 3); +} + +// **CTRD-03** - the zero index rejected. +test ctrd_03_zero_index_rejected { + const ws: [1]CoverWindow = [1]CoverWindow{ + CoverWindow { index: 0, pattern_hash: HASH_01, emission_count: 20, }, + }; + const v = validate_cover_replay(&ws); + assert(v.code == CoverReplayCode.ZeroIndex); + assert(v.idx == 0); +} + +// **CTRD-04** - the zero hash rejected. +test ctrd_04_zero_hash_rejected { + const ws: [1]CoverWindow = [1]CoverWindow{ + CoverWindow { index: 1, pattern_hash: ZERO_HASH32, emission_count: 20, }, + }; + const v = validate_cover_replay(&ws); + assert(v.code == CoverReplayCode.ZeroHash); + assert(v.idx == 0); +} + +// **CTRD-05** - below the emission floor rejected: five of a +// required ten. +test ctrd_05_below_min_rejected { + const ws: [1]CoverWindow = [1]CoverWindow{ + CoverWindow { index: 1, pattern_hash: HASH_01, emission_count: 5, }, + }; + const v = validate_cover_replay(&ws); + assert(v.code == CoverReplayCode.BelowMin); + assert(v.idx == 0); + assert(v.got == 5); + assert(v.bound == 10); +} + +// **CTRD-06** - too many rejected: 257 windows of a maximum 256, +// built in a loop like the original's, the indexes walking one +// upward and the hashes carrying the wrapped walk bytes. The +// batch rule fires before any window is examined. +test ctrd_06_too_many_rejected { + var ws: [257]CoverWindow; + for i in 0..257 { + var h: [32]u8 = [32]u8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,}; + h[0] = CTRD_FLOOD_257[i]; + ws[i] = CoverWindow { index: @as(u64, i) + 1, pattern_hash: h, emission_count: 20, }; + } + const v = validate_cover_replay(&ws); + assert(v.code == CoverReplayCode.TooMany); + assert(v.got == 257); + assert(v.bound == 256); +} + +// **CTRD-07** - the valid batch accepted: three windows, three +// hashes. +test ctrd_07_valid_accepted { + const ws: [3]CoverWindow = [3]CoverWindow{ + CoverWindow { index: 1, pattern_hash: HASH_01, emission_count: 20, }, + CoverWindow { index: 2, pattern_hash: HASH_02, emission_count: 25, }, + CoverWindow { index: 3, pattern_hash: HASH_03, emission_count: 22, }, + }; + const v = validate_cover_replay(&ws); + assert(v.code == CoverReplayCode.Ok); +} + +// **CTRD-08** - an empty batch accepted. +test ctrd_08_empty_accepted { + const ws: [0]CoverWindow = [0]CoverWindow{}; + const v = validate_cover_replay(&ws); + assert(v.code == CoverReplayCode.Ok); +} + +// **CTRD-09** - a single window accepted. +test ctrd_09_single_accepted { + const ws: [1]CoverWindow = [1]CoverWindow{ + CoverWindow { index: 1, pattern_hash: HASH_01, emission_count: 20, }, + }; + const v = validate_cover_replay(&ws); + assert(v.code == CoverReplayCode.Ok); +} + +// **CTRD-10** - the boundary emission count accepted: exactly +// ten. +test ctrd_10_boundary_accepted { + const ws: [1]CoverWindow = [1]CoverWindow{ + CoverWindow { index: 1, pattern_hash: HASH_01, emission_count: 10, }, + }; + const v = validate_cover_replay(&ws); + assert(v.code == CoverReplayCode.Ok); +} + +// The three helpers have direct tests (coverage: Untested 0). +test ctrd_helpers_answer { + assert(hashes_equal(HASH_01, HASH_01)); + assert(!hashes_equal(HASH_01, HASH_02)); + assert(hash_zero(ZERO_HASH32)); + assert(!hash_zero(HASH_01)); + const three: [3]CoverWindow = [3]CoverWindow{ + CoverWindow { index: 1, pattern_hash: HASH_01, emission_count: 20, }, + CoverWindow { index: 2, pattern_hash: HASH_02, emission_count: 20, }, + CoverWindow { index: 3, pattern_hash: HASH_01, emission_count: 20, }, + }; + assert(!hash_seen_before(&three, 0)); + assert(!hash_seen_before(&three, 1)); + assert(hash_seen_before(&three, 2)); +} + +// Rule order is the original's: the zero index before the zero +// hash, the zero hash before the emission floor, the floor +// before the monotonicity, and the monotonicity before the +// repeated hash - with both violations on one window. +test rule_order_matches_original { + const zero_both: [1]CoverWindow = [1]CoverWindow{ + CoverWindow { index: 0, pattern_hash: ZERO_HASH32, emission_count: 5, }, + }; + const v1 = validate_cover_replay(&zero_both); + assert(v1.code == CoverReplayCode.ZeroIndex); + assert(v1.idx == 0); + + const zero_hash_few: [1]CoverWindow = [1]CoverWindow{ + CoverWindow { index: 1, pattern_hash: ZERO_HASH32, emission_count: 5, }, + }; + const v2 = validate_cover_replay(&zero_hash_few); + assert(v2.code == CoverReplayCode.ZeroHash); + assert(v2.idx == 0); + + const few_backstep: [2]CoverWindow = [2]CoverWindow{ + CoverWindow { index: 5, pattern_hash: HASH_01, emission_count: 20, }, + CoverWindow { index: 3, pattern_hash: HASH_02, emission_count: 5, }, + }; + const v3 = validate_cover_replay(&few_backstep); + assert(v3.code == CoverReplayCode.BelowMin); + assert(v3.idx == 1); + + const backstep_dup: [2]CoverWindow = [2]CoverWindow{ + CoverWindow { index: 5, pattern_hash: HASH_01, emission_count: 20, }, + CoverWindow { index: 3, pattern_hash: HASH_01, emission_count: 20, }, + }; + const v4 = validate_cover_replay(&backstep_dup); + assert(v4.code == CoverReplayCode.NotIncreasing); + assert(v4.idx == 1); + + const dup_only: [2]CoverWindow = [2]CoverWindow{ + CoverWindow { index: 5, pattern_hash: HASH_01, emission_count: 20, }, + CoverWindow { index: 6, pattern_hash: HASH_01, emission_count: 20, }, + }; + const v5 = validate_cover_replay(&dup_only); + assert(v5.code == CoverReplayCode.DuplicatePattern); + assert(v5.idx == 1); +}